Django如何配置多会话表为Account模型生成二次认证会话Cookie
1. 定义独立的Account业务会话模型
复用Django内置的AbstractBaseSession抽象基类创建专属Account的会话表,和默认的Auth User临时会话表物理隔离:
# myapp/models.py from django.contrib.sessions.models import AbstractBaseSession from django.db import models from django.contrib.auth.models import PermissionsMixin # 你原有的Account模型 class Account(PermissionsMixin): auth_user = models.ForeignKey("auth.User", on_delete=models.CASCADE) # 其他业务字段... # Account专属会话模型 class AccountSession(AbstractBaseSession): # 可选字段:绑定account_id方便后续查询活跃会话、批量下线等操作 account_id = models.IntegerField(null=True, db_index=True) class Meta: db_table = "account_session"
创建完成后执行python manage.py makemigrations和python manage.py migrate生成新的会话表。
默认的Django内置Session表直接用作Auth User临时认证的存储载体,不需要额外修改。
2. 实现Account专属会话引擎
自定义会话引擎绑定新建的AccountSession模型,继承Django默认的数据库会话逻辑减少重复开发:
# myapp/sessions/backends.py from django.contrib.sessions.backends.db import SessionStore as DBSessionStore from myapp.models import AccountSession class AccountSessionStore(DBSessionStore): # 绑定自定义会话模型 @classmethod def get_model_class(cls): return AccountSession # 重写save方法自动同步account_id到表字段 def save(self, must_create=False): if "account_id" in self: self.model.objects.filter(pk=self.session_key).update(account_id=self["account_id"]) super().save(must_create=must_create)
3. 配置双Cookie参数避免冲突
在settings.py中分别配置两套会话的Cookie规则,从路径、名称、有效期维度完全隔离:
# settings.py # 内置会话配置:对应Auth User临时认证的auth cookie SESSION_COOKIE_NAME = "auth_sessionid" SESSION_COOKIE_PATH = "/auth/" # 仅认证相关接口携带,不会泄露到业务路径 SESSION_COOKIE_AGE = 30 * 60 # 30分钟有效期,仅满足账号选择流程 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 自定义Account业务会话配置 ACCOUNT_SESSION_COOKIE_NAME = "account_sessionid" ACCOUNT_SESSION_COOKIE_PATH = "/" # 全业务路径携带 ACCOUNT_SESSION_COOKIE_AGE = 7 * 24 * 60 * 60 # 7天有效期 ACCOUNT_SESSION_ENGINE = "myapp.sessions.backends.AccountSessionStore"
注意:必须保证两套Cookie的name和path不重叠,避免出现Cookie互相覆盖的问题
4. 适配登录流程逻辑
4.1 Auth User初次认证
该步骤直接使用Django默认的authenticate和login方法即可,认证通过后生成的auth_sessionid Cookie仅能访问/auth路径下的接口,无法访问业务资源。
4.2 查询关联Account列表
校验Auth User会话有效性后,取出当前登录的Auth User ID,查询关联的所有Account列表返回给前端供用户选择。
4.3 生成Account业务会话
用户提交选中的account_id后,先校验Auth会话合法性和Account归属关系,再手动生成Account会话和对应Cookie:
# myapp/views.py from django.conf import settings from django.utils.module_loading import import_string from rest_framework.response import Response from myapp.models import Account AccountSessionStore = import_string(settings.ACCOUNT_SESSION_ENGINE) def select_account(request): # 校验Auth会话合法性 if not request.user.is_authenticated: return Response(status=401) account_id = request.data.get("account_id") # 校验Account属于当前登录的Auth User try: account = Account.objects.get(id=account_id, auth_user=request.user) except Account.DoesNotExist: return Response(status=403) # 初始化Account会话 account_session = AccountSessionStore() account_session["account_id"] = account.id # 可选:预存权限信息减少后续DB查询 account_session["permissions"] = list(account.get_all_permissions()) account_session.save() # 写入Account Cookie到响应 response = Response(status=200) response.set_cookie( key=settings.ACCOUNT_SESSION_COOKIE_NAME, value=account_session.session_key, max_age=settings.ACCOUNT_SESSION_COOKIE_AGE, path=settings.ACCOUNT_SESSION_COOKIE_PATH, secure=request.is_secure(), httponly=True, samesite="Lax" ) return response
5. 业务接口认证适配
自定义中间件校验Account会话,替换默认的Auth认证逻辑,所有业务接口都通过该中间件校验权限:
# myapp/middlewares.py from django.conf import settings from django.utils.module_loading import import_string from rest_framework.response import Response from myapp.models import Account AccountSessionStore = import_string(settings.ACCOUNT_SESSION_ENGINE) class AccountAuthMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # 跳过/auth路径下的认证接口 if request.path.startswith("/auth/"): return self.get_response(request) # 校验Account Cookie有效性 account_session_key = request.COOKIES.get(settings.ACCOUNT_SESSION_COOKIE_NAME) if not account_session_key: return Response(status=401) account_session = AccountSessionStore(session_key=account_session_key) if not account_session.exists(account_session_key): return Response(status=401) # 注入当前Account实例到request对象,业务逻辑直接调用 request.account = Account.objects.get(id=account_session["account_id"]) request.account_permissions = account_session["permissions"] return self.get_response(request)
将该中间件添加到settings.py的MIDDLEWARE列表中,放在django.middleware.common.CommonMiddleware之后即可。
6. 权限适配
因为Account模型继承了PermissionsMixin,你可以自定义权限装饰器校验request.account的权限,也可以在中间件中将request.user临时替换为Account实例,直接复用Django原生的权限校验逻辑。
内容的提问来源于stack exchange,提问作者Karthik

