Django 状态管理与请求处理:Cookie、Session、CSRF 和中间件详解

一、Cookie、Session 与登录状态

HTTP 协议本身是无状态的:一次请求结束后,服务器不会自动知道下一次请求是否来自同一个用户。登录功能需要额外的机制保存身份信息,Cookie 和 Session 就是 Django 中最常见的一组方案。

Cookie 是浏览器保存的小段键值数据。服务器通过响应头 Set-Cookie 写入,浏览器在后续请求的 Cookie 请求头中自动带回。它适合保存非敏感的偏好设置或一个随机的会话标识,不应该直接保存明文密码。

Session 的数据主要保存在服务器端,浏览器通常只保存 sessionid。请求到达 Django 后,Session 中间件根据这个标识找到服务器端的数据,再通过 request.session 提供给视图使用。默认情况下,Session 数据存储在数据库的 django_session 表中。

Token 是另一种登录状态方案,常用于前后端分离和 API。服务器签发一个经过签名或加密的令牌,客户端之后在请求头中携带。Token 并不等同于 Session:它通常由应用或认证库管理,具体格式和存储方式取决于实现。

可以这样理解三者的区别:Cookie 是浏览器端的存储载体,Session 是服务器端的会话数据,Token 是一种可独立传递的身份凭证。生产环境应使用 HTTPS,并合理设置 HttpOnlySecureSameSite 等属性。

二、Django 操作 Cookie

1. 设置 Cookie

视图返回 HttpResponserender 返回的响应对象或重定向对象后,都可以调用 set_cookie()。Cookie 的值应尽量是简单字符串,不要把密码等敏感信息直接写入浏览器。

from django.http import HttpResponse


def login(request):
    username = request.POST.get("username")
    password = request.POST.get("password")

    if username == "dream" and password == "666":
        response = HttpResponse("登录成功")
        response.set_cookie(
            "login_user",
            username,
            max_age=60 * 60 * 24,  # 最长保存 1 天,单位为秒
            httponly=True,         # 禁止 JavaScript 直接读取
            samesite="Lax",        # 限制跨站请求携带 Cookie
        )
        return response

    return HttpResponse("用户名或密码错误", status=401)

max_age 表示从当前时刻开始的有效秒数。对于登录状态,更推荐保存随机会话标识,而不是保存用户名、密码或可推导出密码的内容。Secure=True 可以要求浏览器仅在 HTTPS 连接中发送 Cookie,正式环境应启用它。

2. 读取 Cookie

请求对象的 COOKIES 是一个类似字典的对象。Cookie 来自客户端,因此只能作为线索使用,不能单独作为权限判断依据。

from django.http import HttpResponse


def profile(request):
    username = request.COOKIES.get("login_user")
    if not username:
        return HttpResponse("请先登录", status=401)
    return HttpResponse(f"当前用户:{username}")

3. 删除 Cookie 和设置过期时间

删除 Cookie 时,Django 会返回一个过期的 Set-Cookie 响应头,浏览器收到后才会移除对应数据。删除时应使用与原 Cookie 相同的 pathdomain 配置。

from django.http import HttpResponse


def logout(request):
    response = HttpResponse("已退出登录")
    response.delete_cookie("login_user", path="/")
    return response

max_age=0 可以让 Cookie 立即过期;不设置 max_ageexpires 时,通常是浏览器关闭即失效的会话 Cookie。浏览器的具体保留行为还会受到 Cookie 属性和隐私策略影响。

三、Django 操作 Session

使用数据库 Session 前,确认 django.contrib.sessions 已加入 INSTALLED_APPS,并执行迁移:

python manage.py migrate

同时需要启用以下中间件。它负责读取请求中的 Session Cookie,并在响应阶段保存 Session 的变化:

MIDDLEWARE = [
    # ...
    "django.contrib.sessions.middleware.SessionMiddleware",
    # ...
]

1. 写入和读取 Session

from django.http import HttpResponse


def session_login(request):
    username = request.POST.get("username")
    password = request.POST.get("password")

    if username == "dream" and password == "666":
        # Django 会在服务器端保存数据,并向浏览器设置 sessionid。
        request.session["username"] = username
        request.session["is_login"] = True
        return HttpResponse("登录成功")

    return HttpResponse("登录失败", status=401)


def dashboard(request):
    if not request.session.get("is_login"):
        return HttpResponse("请先登录", status=401)

    username = request.session.get("username")
    return HttpResponse(f"欢迎,{username}")

Session 的键值接口与字典类似,但实际数据由 Django 的 Session 引擎管理。不要把未经保护的密码放入 Session;登录场景应使用 Django 的认证系统和密码哈希工具。

2. 删除、清空和设置过期时间

def session_logout(request):
    # 只删除一个键,其他 Session 数据仍然保留。
    request.session.pop("is_login", None)
    request.session.pop("username", None)
    return HttpResponse("已退出登录")


def clear_session(request):
    # 删除当前会话的所有数据,并使当前 sessionid 失效。
    request.session.flush()
    return HttpResponse("会话已清空")


def set_session_expiry(request):
    request.session.set_expiry(60 * 30)  # 30 分钟无操作后过期
    return HttpResponse("会话有效期已设置")

request.session.delete() 通常用于删除当前 Session 记录;flush() 会同时清除数据并生成新的会话标识,更适合退出登录或防止会话固定攻击的场景。set_expiry(0) 表示浏览器关闭时过期,传入 None 则恢复项目的默认策略。

四、为 CBV 添加 CSRF 装饰器

CSRF(跨站请求伪造)攻击会诱导已登录用户向目标网站发送非预期请求。Django 默认通过 CsrfViewMiddleware 保护修改数据的请求。HTML 表单应包含 {% csrf_token %},API 则可以根据认证方式设计对应的 CSRF 策略。

csrf_protect 用于显式启用 CSRF 校验,csrf_exempt 用于豁免校验。豁免会降低安全性,只应在明确了解风险并有其他防护措施时使用。

函数视图可以直接使用装饰器;CBV 的方法不是普通函数,需要借助 method_decorator。将装饰器加在 dispatch 上,表示该类的所有请求方法都受到影响;指定 name="post" 则只作用于 POST 方法。

from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.http import HttpResponse


@csrf_protect
def function_view(request):
    return HttpResponse("函数视图已启用 CSRF 校验")


@method_decorator(csrf_protect, name="post")
class LoginView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse("登录页面")

    def post(self, request, *args, **kwargs):
        return HttpResponse("登录请求已通过 CSRF 校验")


@method_decorator(csrf_exempt, name="dispatch")
class WebhookView(View):
    def post(self, request, *args, **kwargs):
        # 只有在 webhook 使用签名校验等替代方案时才考虑豁免。
        return HttpResponse("Webhook 已接收")

直接在 CBV 的 post() 方法上写普通的 @csrf_exempt,通常不会按预期工作,因为 Django 最终调用的是经过 as_view() 暴露的类视图入口。使用 method_decorator 可以明确地把函数装饰器适配到类视图。

五、中间件与请求生命周期

中间件是位于请求和视图之间的一层可插拔处理逻辑。请求进入时,中间件可以检查或修改 request;视图返回响应后,中间件还可以修改 response。多个中间件按照 MIDDLEWARE 的顺序进入,响应阶段通常按相反顺序返回。

常见内置中间件的职责:

  • SecurityMiddleware:提供 HTTPS 重定向、HSTS 等安全相关处理。
  • SessionMiddleware:加载和保存 request.session
  • CommonMiddleware:处理规范化 URL、请求头等通用逻辑。
  • CsrfViewMiddleware:校验 CSRF Token。
  • AuthenticationMiddleware:在 Session 基础上提供 request.user
  • MessageMiddleware:支持 Django 消息框架。
  • XFrameOptionsMiddleware:限制页面被嵌入 iframe,降低点击劫持风险。

顺序很重要:例如 AuthenticationMiddleware 依赖 SessionMiddleware,因此必须放在它后面。

1. 自定义中间件

现代 Django 推荐编写一个接收 get_response 的可调用对象。下面的例子记录请求耗时,并把结果写入响应头。

# users/middleware.py
import time


class RequestTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start = time.perf_counter()
        response = self.get_response(request)
        elapsed = time.perf_counter() - start
        response["X-Request-Time"] = f"{elapsed:.4f}s"
        return response

get_response(request) 会继续调用下一个中间件或最终视图。必须返回一个响应对象,否则 Django 无法完成请求。中间件应保持职责单一,避免在其中堆积具体业务逻辑。

2. 注册自定义中间件

# settings.py
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "users.middleware.RequestTimingMiddleware",
]

路径必须写成“应用名.模块名.类名”。修改 MIDDLEWARE 后应重启开发服务器,并确认该类可以被 Django 正常导入。

3. 观察执行顺序

如果需要学习或排查中间件顺序,可以临时打印不同阶段的日志:

from django.utils.deprecation import MiddlewareMixin


class DebugMiddleware(MiddlewareMixin):
    def process_request(self, request):
        print("1. process_request")

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("2. process_view")

    def process_response(self, request, response):
        print("3. process_response")
        return response

process_request 在视图执行前调用,process_view 紧接在视图调用前,process_response 在响应返回客户端前调用。调试完成后应移除 print,改用日志系统记录必要信息。

六、总结与练习方向

  1. Cookie 存在浏览器端,Session 数据主要存在服务器端;二者通常通过一个 Session Cookie 联系起来。
  2. 登录状态不应依赖客户端可随意修改的明文信息,权限判断应使用可靠的服务端会话或认证机制。
  3. CSRF 保护默认应保持开启;CBV 使用 method_decorator 指定装饰器作用范围。
  4. 中间件适合做认证、日志、统一响应头和异常处理等横切逻辑,执行顺序由 MIDDLEWARE 配置决定。
  5. 可以在图书管理系统中加入注册、登录、退出功能,并使用 Session 保护“新增、修改、删除图书”等页面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值