Django DRF如何保存用户设备避免每次登录都需执行2FA验证
DRF JWT 受信任设备2FA跳过实现方案
核心逻辑
用户首次在新设备完成2FA验证后,服务端为该设备签发唯一加密标识,存储为HttpOnly安全Cookie,后续同一设备登录时,校验标识有效则直接返回JWT凭证,跳过OTP发送流程。
具体实现步骤
1. 新增受信任设备存储模型
在项目models.py中新增UserTrustedDevice表,用于存储用户受信任设备信息:
from django.db import models from django.contrib.auth import get_user_model from django.utils import timezone User = get_user_model() class UserTrustedDevice(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='trusted_devices') device_identifier = models.CharField(max_length=255, unique=True) # 设备唯一标识 device_name = models.CharField(max_length=100, default='未知设备') # 可选,用户备注设备名 expired_at = models.DateTimeField() # 信任有效期 created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('user', 'device_identifier')
2. 修改登录逻辑,优先校验受信任设备
修改usecases.py中的UserLoginWithOTPUseCase._factory方法,用户名密码校验通过后,先检查是否存在有效受信任设备,存在则直接返回JWT,跳过OTP发送:
from django.utils import timezone from .models import UserTrustedDevice from rest_framework_simplejwt.tokens import RefreshToken class UserLoginWithOTPUseCase(CreateUseCase, OTPMixin): # 原有__init__、execute方法保持不变 def _factory(self): credentials = { 'username': self._data['email'], 'password': self._data['password'] } self._user = authenticate(self._request, **credentials) if self._user is not None: # 新增:校验有效受信任设备 trusted_device_token = self._request.COOKIES.get('trusted_device_token') if trusted_device_token: valid_device = UserTrustedDevice.objects.filter( user=self._user, device_identifier=trusted_device_token, expired_at__gt=timezone.now() ).first() if valid_device: # 直接返回JWT,跳过2FA refresh = RefreshToken.for_user(self._user) # 这里把结果返回给视图层构造响应即可 return { 'access': str(refresh.access_token), 'refresh': str(refresh), 'skip_2fa': True } # 无有效受信任设备,走原有OTP发送逻辑 code = self._generate_totp( user=self._user, purpose='2FA', interval=180 ) EmailVerificationEmail( context={ 'code': code, 'uuid': self._user.id } ).send(to=[self._user.email]) else: raise PermissionDenied( { 'authentication_error': _('User name or password not matched') } )
3. OTP验证通过后新增受信任设备记录
在OTP验证接口的逻辑中,新增remember_device参数,用户勾选后生成设备标识并写入Cookie:
# OTP验证接口逻辑示例(你原有OTP校验通过的位置新增以下代码) import secrets from datetime import timedelta from django.conf import settings # 假设已经完成OTP校验,拿到当前登录用户user remember_device = request.data.get('remember_device', False) response = Response({ 'access': str(refresh.access_token), 'refresh': str(refresh) }) if remember_device: # 生成随机唯一设备标识 device_id = secrets.token_urlsafe(32) # 存储到受信任设备表 UserTrustedDevice.objects.create( user=user, device_identifier=device_id, expired_at=timezone.now() + timedelta(days=30), # 信任有效期30天 device_name=request.data.get('device_name', '未知设备') ) # 写入HttpOnly安全Cookie response.set_cookie( 'trusted_device_token', device_id, max_age=30*24*60*60, httponly=True, secure=not settings.DEBUG, # 生产环境强制HTTPS传输 samesite='Lax' ) return response
安全注意事项
- 受信任设备Cookie必须开启HttpOnly属性,防止XSS攻击窃取标识,生产环境必须开启Secure属性,仅HTTPS请求携带
- 设备标识使用随机加密串,不要存储IP、UA等明文可篡改的信息,避免伪造
- 提供用户设备管理入口,支持手动删除受信任设备,账号异常时可以一键清空所有信任记录
- 信任有效期建议设置为30-90天,到期自动失效,需要重新完成2FA验证
内容的提问来源于stack exchange,提问作者lord stock
相关产品推荐
相关产品推荐

