基于Djoser JWT实现登录同步获取验证令牌及登出流程的方法咨询
实现JWT登录验证与安全登出流程(基于Djoser)
一、登录时同时获取并验证JWT令牌
Djoser默认的/jwt/create/只返回令牌,要实现“获取+验证”的合并流程,有两种方式:
前端直接联动现有端点
无需修改后端,前端完成两次请求即可:
- 调用
POST /jwt/create/,传入username和password,拿到access和refresh令牌 - 立即发起
POST /jwt/verify/,请求体携带{"token": "<access_token>"},验证令牌有效性 - 验证通过后,将令牌存入
HttpOnly Cookie(推荐)或localStorage,完成登录流程
后端自定义合并端点
如果想让前端一次请求完成操作,可自定义视图合并令牌生成与验证逻辑:
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenVerifySerializer class LoginWithVerifyView(APIView): def post(self, request): # 生成JWT令牌 obtain_serializer = TokenObtainPairSerializer(data=request.data) obtain_serializer.is_valid(raise_exception=True) tokens = obtain_serializer.validated_data # 验证access令牌有效性 verify_serializer = TokenVerifySerializer(data={"token": tokens["access"]}) verify_serializer.is_valid(raise_exception=True) return Response(tokens)
在urls.py中添加路由:
path('jwt/login-verify/', LoginWithVerifyView.as_view(), name='login_with_verify'),
前端直接请求该端点即可获取已验证的令牌。
二、登出时用刷新令牌处理(含令牌更新与失效)
Djoser没有内置JWT登出端点,因为JWT本身是无状态的,需手动处理令牌生命周期。针对你的需求,分两种场景实现:
场景1:登出前用刷新令牌获取新访问令牌
如果需要在登出前更新一次访问令牌(比如收尾操作需要有效令牌),前端可先调用刷新端点再清除本地令牌:
async function handleLogout() { // 从Cookie中获取refresh令牌(推荐用Cookie存储) const refreshToken = document.cookie.split('; ') .find(row => row.startsWith('refresh_token='))?.split('=')[1]; // 先请求新的access令牌 if (refreshToken) { await fetch('/jwt/refresh/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({"refresh": refreshToken}) }); } // 清除客户端存储的所有令牌 document.cookie = 'access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; document.cookie = 'refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; window.location.href = '/login/'; }
场景2:安全登出(失效刷新令牌)
要彻底禁止已登出用户用旧刷新令牌获取新令牌,需在后端存储有效刷新令牌列表,登出时移除对应令牌:
- 创建存储有效刷新令牌的模型
from django.db import models from django.contrib.auth import get_user_model from datetime import datetime User = get_user_model() class ActiveRefreshToken(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) token = models.CharField(max_length=500, unique=True) expires_at = models.DateTimeField() class Meta: indexes = [models.Index(fields=['token'])]
- 扩展令牌生成视图,存入新刷新令牌
from rest_framework_simplejwt.views import TokenObtainPairView from rest_framework_simplejwt.tokens import RefreshToken class CustomTokenObtainPairView(TokenObtainPairView): def post(self, request, *args, **kwargs): response = super().post(request, *args, **kwargs) refresh_token = response.data.get('refresh') if refresh_token: token_obj = RefreshToken(refresh_token) ActiveRefreshToken.objects.create( user=self.request.user, token=refresh_token, expires_at=datetime.fromtimestamp(token_obj['exp']) ) return response
- 自定义登出视图,移除刷新令牌
from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework_simplejwt.exceptions import InvalidToken class LogoutView(APIView): permission_classes = [IsAuthenticated] def post(self, request): refresh_token = request.data.get('refresh') if not refresh_token: return Response({"error": "Refresh token required"}, status=400) try: ActiveRefreshToken.objects.get(token=refresh_token).delete() return Response({"detail": "Successfully logged out"}, status=200) except ActiveRefreshToken.DoesNotExist: raise InvalidToken("Refresh token is invalid or already revoked")
- 扩展刷新视图,验证令牌有效性
from rest_framework_simplejwt.views import TokenRefreshView from rest_framework_simplejwt.exceptions import InvalidToken class CustomTokenRefreshView(TokenRefreshView): def post(self, request, *args, **kwargs): refresh_token = request.data.get('refresh') if not ActiveRefreshToken.objects.filter(token=refresh_token).exists(): raise InvalidToken("Refresh token has been revoked") return super().post(request, *args, **kwargs)
- 更新路由,替换默认端点
from django.urls import path from .views import CustomTokenObtainPairView, CustomTokenRefreshView, LogoutView urlpatterns = [ path('jwt/create/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'), path('jwt/refresh/', CustomTokenRefreshView.as_view(), name='token_refresh'), path('jwt/logout/', LogoutView.as_view(), name='logout'), # 保留Djoser其他端点... ]
前端登出时,只需调用POST /jwt/logout/并传入refresh令牌,后端会将其从有效列表中移除,之后该令牌无法再用于刷新获取新的access令牌。
内容的提问来源于stack exchange,提问作者zoro
相关产品推荐
相关产品推荐

