一、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,并合理设置 HttpOnly、Secure、SameSite 等属性。
二、Django 操作 Cookie
1. 设置 Cookie
视图返回 HttpResponse、render 返回的响应对象或重定向对象后,都可以调用 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 相同的 path 和 domain 配置。
from django.http import HttpResponse
def logout(request):
response = HttpResponse("已退出登录")
response.delete_cookie("login_user", path="/")
return response
max_age=0 可以让 Cookie 立即过期;不设置 max_age 或 expires 时,通常是浏览器关闭即失效的会话 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,改用日志系统记录必要信息。
六、总结与练习方向
- Cookie 存在浏览器端,Session 数据主要存在服务器端;二者通常通过一个 Session Cookie 联系起来。
- 登录状态不应依赖客户端可随意修改的明文信息,权限判断应使用可靠的服务端会话或认证机制。
- CSRF 保护默认应保持开启;CBV 使用
method_decorator指定装饰器作用范围。 - 中间件适合做认证、日志、统一响应头和异常处理等横切逻辑,执行顺序由
MIDDLEWARE配置决定。 - 可以在图书管理系统中加入注册、登录、退出功能,并使用 Session 保护“新增、修改、删除图书”等页面。

394

被折叠的 条评论
为什么被折叠?



