Django如何生成唯一用户Token?DRF Token登录网关问题求助
嘿,我来帮你理清这个问题的解决思路,分两部分给你说明:
1. DRF Token 是否每个用户唯一?
没错,DRF 自带的 Token 模型(来自 rest_framework.authtoken.models)默认和用户模型是一对一绑定的——每个用户只会对应一个 Token,除非你手动硬给同一个用户创建多个(这完全没必要,反而不安全),所以正常情况下每个用户的 Token 是唯一的。
不过这里要敲个重点:你在自定义的 MyUser 模型里加了自己的 token 字段,这和 DRF 自带的 Token 是完全分开的两个东西!DRF 的 Token 存在单独的 authtoken_token 数据库表中,而你自己的 token 字段是存在 MyUser 表的,两者没有任何关联。这很可能就是你遇到「MyUser匹配查询不存在」错误的核心原因——你用 DRF 返回的 Token(来自 DRF 的 Token 表)去查 MyUser 的 token 字段,当然找不到对应的用户啦!
2. 如何生成 Token 并适配你的登录网关?
根据你的需求,有两种靠谱方案,推荐优先用 DRF 自带的 Token 系统,省心又安全:
方案一:用 DRF 自带的 Token 系统(推荐)
第一步:先配置好 DRF Token 认证
- 打开
settings.py,把 DRF 的 Token 应用加进去:
INSTALLED_APPS = [ # ... 你已有的其他应用 'rest_framework', 'rest_framework.authtoken', # 新增这一行 ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', # 如果你还需要其他认证方式(比如session),可以继续加 ] }
- 执行数据库迁移,创建 DRF 用来存 Token 的表:
python manage.py migrate
第二步:为用户生成 Token
有两种方式生成:
- 新用户自动生成:用 Django 信号量实现,在你的
MyUser模型文件(比如business_accounts/models/my_user.py)里加这段代码:
from django.db.models.signals import post_save from django.dispatch import receiver from rest_framework.authtoken.models import Token from django.conf import settings @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_auth_token(sender, instance=None, created=False, **kwargs): # 当用户被创建时,自动生成对应的Token if created: Token.objects.create(user=instance)
- 给已有用户补生成 Token:如果是已经存在的用户,直接在 Django shell 里跑:
from rest_framework.authtoken.models import Token from business_accounts.models.my_user import MyUser user = MyUser.objects.get(user_id=52) # 替换成你的用户ID token, created = Token.objects.get_or_create(user=user) print(token.key) # 这个就是用户的Token,和你之前DRF返回的格式一致
第三步:修改你的登录网关视图
现在要从 DRF 的 Token 表里查询用户,而不是查 MyUser 的 token 字段,还要加异常处理避免崩溃:
from django.contrib.auth import login from django.core.urlresolvers import reverse from django.views.generic import View from django.http import HttpResponseRedirect from rest_framework.authtoken.models import Token # 导入DRF的Token模型 class UrlGatewayLogin(View): def get(self, request, **kwargs): page_group = kwargs.get('page_group') token_key = request.GET.get('token') try: # 通过Token的key找到对应的Token记录,再关联到用户 token = Token.objects.get(key=token_key) user = token.user except Token.DoesNotExist: # Token无效的话,跳转到登录页或者错误页,这里你自己调整 return HttpResponseRedirect(reverse('login')) # 必须指定backend才能正常登录 user.backend = 'django.contrib.auth.backends.ModelBackend' login(request, user) return HttpResponseRedirect(reverse('dashboard', args=(page_group, )))
方案二:用你自定义的 MyUser.token 字段
如果你坚持要用自己加的 token 字段,那得确保 Token 正确生成并保存到用户实例里:
第一步:生成并保存自定义 Token
用 Python 的 secrets 模块生成安全的随机字符串(比普通随机方法更安全),同样可以用信号量自动生成:
from django.db.models.signals import post_save from django.dispatch import receiver import secrets from business_accounts.models.my_user import MyUser @receiver(post_save, sender=MyUser) def create_custom_token(sender, instance=None, created=False, **kwargs): if created and not instance.token: # 生成40位的随机Token(和DRF默认的Token长度一致) instance.token = secrets.token_hex(20) instance.save()
要是给已有用户补生成,就手动跑:
import secrets from business_accounts.models.my_user import MyUser user = MyUser.objects.get(user_id=52) user.token = secrets.token_hex(20) user.save()
第二步:修复视图的查询逻辑
一定要加异常处理,避免因为无效 Token 直接抛错:
from django.contrib.auth import login from django.core.urlresolvers import reverse from django.views.generic import View from django.http import HttpResponseRedirect from business_accounts.models.my_user import MyUser class UrlGatewayLogin(View): def get(self, request, **kwargs): page_group = kwargs.get('page_group') token = request.GET.get('token') try: user = MyUser.objects.get(token=token) except MyUser.DoesNotExist: # Token无效时的处理,比如跳登录页 return HttpResponseRedirect(reverse('login')) user.backend = 'django.contrib.auth.backends.ModelBackend' login(request, user) return HttpResponseRedirect(reverse('dashboard', args=(page_group, )))
最后再复盘下你之前的错误原因
你用 DRF 返回的 Token 是 DRF 自带 Token 表中的值,但你的 MyUser.token 字段根本没存这个值,所以当你用这个 Token 去查 MyUser 时,自然找不到对应的用户,就抛出了「匹配查询不存在」的错误。只要把查询逻辑对应到正确的 Token 来源(要么 DRF 的 Token 表,要么自己的 MyUser.token 字段),问题就解决啦!
内容的提问来源于stack exchange,提问作者copser

