Django中如何通过邮件访问码链接实现跨设备浏览器的用户会话保持
Django跨设备/浏览器会话保持实现方案
你提到的加密request ID方案是可行的,另外还有更简便的基于Django内置签名的无状态方案,无需额外开发API,两种方案的实现细节如下:
方案1:内置签名无状态实现(更优,无需额外存储)
该方案直接用Django自带的signing模块对用户验证信息签名,签名后的字符串直接放在跳转链接参数中,后端直接校验签名有效性即可,无需查库/查缓存,性能更高。
实现步骤
- 生成带签名的跳转链接
用户提交信息验证通过后,把需要保留的用户标识、验证状态、有效期等信息打包成字典,签名后拼接为跳转链接参数,随邮件发送import time from django.core import signing from django.core.mail import send_mail # 构造需要保留的用户验证信息 payload = { "user_id": 123, # 替换为实际的用户唯一标识 "verify_passed": True, "expire_at": int(time.time()) + 3600 * 24 # 签名24小时内有效 } # 生成防篡改的签名字符串 signed_payload = signing.dumps(payload) # 拼接跳转链接 redirect_url = f"https://你的域名.com/verify/callback?signed={signed_payload}" # 发送邮件逻辑 send_mail( subject="验证通过通知", message=f"点击链接继续操作:{redirect_url}", from_email="admin@你的域名.com", recipient_list=[用户邮箱], ) - 回调页校验签名完成会话创建
编写回调视图处理跳转请求,校验签名有效后直接为当前设备/浏览器创建有效会话from django.core import signing from django.shortcuts import redirect from django.contrib.auth import login from django.contrib.auth.models import User def verify_callback(request): signed_payload = request.GET.get("signed") if not signed_payload: return redirect("/verify") try: # 校验签名有效性,同时限制最大有效期为1天 payload = signing.loads(signed_payload, max_age=3600*24) except signing.BadSignature: # 签名篡改/过期,跳回验证页 return redirect("/verify") # 签名有效,为当前设备创建会话 user = User.objects.get(id=payload["user_id"]) login(request, user) # 跳转到业务需要的原页面 return redirect("/目标页面")
方案2:Request ID存储方案实现
如果需要携带的payload内容较多,不适合放在URL参数中,可以选择你提到的存储request ID的方案:
实现步骤
- 验证通过后存储payload并生成加密链接
import uuid from django.core import signing from django.core.cache import cache payload = { "user_id": 123, "verify_passed": True, "target_url": "/原页面路径" } # 生成唯一request ID request_id = str(uuid.uuid4()) # 将payload存入缓存,设置24小时过期 cache.set(f"verify_request:{request_id}", payload, timeout=3600*24) # 加密request ID防止被遍历 encrypted_request_id = signing.dumps(request_id) # 拼接跳转链接 redirect_url = f"https://你的域名.com/verify/callback?req_id={encrypted_request_id}" - 回调页解密查询payload
from django.core import signing from django.shortcuts import redirect from django.core.cache import cache from django.contrib.auth import login from django.contrib.auth.models import User def verify_callback(request): encrypted_req_id = request.GET.get("req_id") if not encrypted_req_id: return redirect("/verify") try: request_id = signing.loads(encrypted_req_id, max_age=3600*24) except signing.BadSignature: return redirect("/verify") # 从缓存查询对应payload payload = cache.get(f"verify_request:{request_id}") if not payload: return redirect("/verify") # 为当前设备创建会话 user = User.objects.get(id=payload["user_id"]) login(request, user) # 用完删除缓存,防止链接被重复使用 cache.delete(f"verify_request:{request_id}") return redirect(payload["target_url"])
注意事项
- 所有签名必须设置有效期,避免信息泄露后被长期滥用
- 涉及敏感操作的场景,签名/request ID使用后立即做失效处理,防止重复点击链接重复使用
- 不要在payload中存储密码等敏感信息,签名仅实现防篡改能力,不属于加密存储
内容的提问来源于stack exchange,提问作者ninjacode
相关产品推荐
相关产品推荐

