如何基于django-rest-framework-jwt实现一次性验证码免密自动登录并获取JWT?
实现无用户名密码的JWT登录(基于Django 2 + DRF + JWT)
当然可以实现啦!默认的django-rest-framework-jwt确实依赖用户名密码来生成Token,但我们可以通过扩展逻辑,结合一次性登录令牌(也就是你说的生成码),完全实现无密码登录并获取JWT的需求。下面是具体的实现步骤:
1. 先做基础:添加一次性登录令牌模型
首先需要一个模型来存储一次性的登录凭证,用来关联用户、记录令牌状态和过期时间:
# models.py from django.db import models from django.contrib.auth.models import User import uuid from datetime import timedelta from django.utils import timezone class OneTimeLoginToken(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) token = models.UUIDField(default=uuid.uuid4, unique=True) # 用UUID生成不可猜测的令牌 created_at = models.DateTimeField(auto_now_add=True) expires_at = models.DateTimeField() is_used = models.BooleanField(default=False) # 标记令牌是否已使用 def save(self, *args, **kwargs): # 默认设置令牌1小时后过期,可根据需求调整时长 if not self.expires_at: self.expires_at = timezone.now() + timedelta(hours=1) super().save(*args, **kwargs) def is_valid(self): # 验证令牌是否有效:未使用且未过期 return not self.is_used and timezone.now() < self.expires_at
2. 生成带令牌的登录链接
创建一个视图来生成包含令牌的链接,比如你可以在用户需要无密码登录时(比如找回密码、系统主动邀请)调用这个接口,把链接通过邮件/短信发给用户:
# views.py from django.http import HttpResponse from django.contrib.auth.models import User from .models import OneTimeLoginToken def generate_login_link(request, user_id): try: user = User.objects.get(id=user_id) # 先清理该用户已有的未使用令牌,避免重复生成 OneTimeLoginToken.objects.filter(user=user, is_used=False).delete() # 创建新的一次性令牌 token_obj = OneTimeLoginToken.objects.create(user=user) # 构造前端登录页面的链接,把令牌作为参数带上 login_link = f"https://你的前端域名.com/login?token={token_obj.token}" # 这里可以添加发送邮件/短信的逻辑,把login_link推送给用户 return HttpResponse(f"登录链接已生成:{login_link}") except User.DoesNotExist: return HttpResponse("目标用户不存在", status=404)
3. 扩展JWT逻辑:验证令牌并生成JWT
接下来需要自定义一个DRF视图,接收前端传来的一次性令牌,验证通过后直接生成JWT返回给前端:
# views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from .models import OneTimeLoginToken from rest_framework_jwt.settings import api_settings # 导入JWT的核心处理函数 jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER class OneTimeLoginView(APIView): def post(self, request): token_str = request.data.get('token') if not token_str: return Response({"error": "登录令牌不能为空"}, status=status.HTTP_400_BAD_REQUEST) try: token_obj = OneTimeLoginToken.objects.get(token=token_str) # 验证令牌是否有效 if not token_obj.is_valid(): return Response({"error": "令牌已过期或已被使用"}, status=status.HTTP_400_BAD_REQUEST) # 标记令牌为已使用,防止重复登录 token_obj.is_used = True token_obj.save() # 生成JWT Token payload = jwt_payload_handler(token_obj.user) jwt_token = jwt_encode_handler(payload) return Response({ "token": jwt_token, "user_id": token_obj.user.id, "username": token_obj.user.username }, status=status.HTTP_200_OK) except OneTimeLoginToken.DoesNotExist: return Response({"error": "无效的登录令牌"}, status=status.HTTP_404_NOT_FOUND)
4. 配置URL路由
把上面的视图添加到项目路由中,让接口可以被访问:
# urls.py from django.urls import path from .views import generate_login_link, OneTimeLoginView urlpatterns = [ # 生成登录链接的接口(建议只给内部/管理员调用,不要对外开放) path('generate-login-link/<int:user_id>/', generate_login_link, name='generate-login-link'), # 验证令牌并获取JWT的接口 path('api/login/one-time/', OneTimeLoginView.as_view(), name='one-time-login'), ]
几个关键注意事项
- 令牌安全性:一定要用UUID这类不可猜测的字符串做令牌,同时设置合理的过期时间(比如1小时),降低被滥用的风险。
- 权限控制:生成登录链接的
generate-login-link接口必须严格控制权限,比如只允许管理员或者系统内部逻辑调用,绝对不能对外开放。 - JWT配置:可以在
settings.py里调整JWT的过期时间等参数,比如:
# settings.py JWT_AUTH = { 'JWT_EXPIRATION_DELTA': timedelta(days=7), # 设置JWT的有效期 'JWT_ALLOW_REFRESH': True, # 允许刷新Token }
这样一来,用户点击带令牌的链接后,前端把令牌发送到/api/login/one-time/接口,就能拿到JWT Token,之后就可以用这个Token进行正常的DRF接口认证了。
内容的提问来源于stack exchange,提问作者frank
相关产品推荐
相关产品推荐

