Django two-factor-auth多设备登录验证及延长TOTP码有效期问题
问题根因分析
- 新设备登录无二次验证:当前代码逻辑仅判断用户是否存在已绑定的TOTP设备,未对登录设备的身份做标识和校验,也没有维护可信设备列表,只要用户有已确认的TOTP设备,任意设备登录都会直接跳过二次验证流程;同时TOTP设备创建逻辑仅在用户无设备时触发,不会针对新设备生成新的验证要求。
- TOTP step调整后验证码失效:当前仅修改了pyotp生成验证码时的step参数,没有同步修改TOTPDevice模型存储的step字段,验证时调用的
device.verify_token方法会读取设备自身存储的step默认值(30秒),生成和验证的step参数不一致导致校验失败。
修复方案
1. 新设备二次验证逻辑修改
首先需要新增可信设备标识逻辑,对每个登录请求生成设备指纹(可将UA、IP等信息哈希后生成唯一标识),维护可信设备表存储用户ID、设备指纹、信任时间等信息。登录时先校验当前设备指纹是否在对应用户的可信列表中,不在则强制触发二次验证,验证通过后将当前设备加入可信列表。
调整后的核心代码如下:
import hashlib # 新增生成设备指纹的工具方法 def get_device_fingerprint(request): ua = request.META.get('HTTP_USER_AGENT', '') ip = request.META.get('REMOTE_ADDR', '') return hashlib.sha256(f"{ua}{ip}".encode()).hexdigest() # 调整get_user_totp_device逻辑,保留原有功能 def get_user_totp_device(self, user, confirmed=None): """ Gets user's time based one-time password devices """ devices = devices_for_user(user, confirmed=confirmed) for device in devices: if isinstance(device, TOTPDevice): return device class TOTPCreateView(APIView): permission_classes = [] def post(self, request): username = request.data['username'] user = User.objects.get(username=username) if user.is_anonymous: return Response({}, status=status.HTTP_401_UNAUTHORIZED) # 校验当前设备是否已经信任 device_fingerprint = get_device_fingerprint(request) # TrustedDevice为需要新增的模型,字段参考:user(外键到User)、fingerprint(字符型)、created_at(日期型) is_trusted = TrustedDevice.objects.filter(user=user, fingerprint=device_fingerprint).exists() if is_trusted: # 已信任设备直接返回无需验证 return Response({"need_verify": False}, status=status.HTTP_200_OK) # 未信任设备,获取现有TOTP设备生成验证挑战 device = get_user_totp_device(self, user) if not device: # 用户首次绑定,创建设备时指定step参数,比如60秒 device = user.totpdevice_set.create(confirmed=False, step=60) # 生成验证码时同步step参数 secret_key = device.bin_key totp = pyotp.TOTP(base64.b32encode(secret_key), step=device.step) one_time_password = totp.now() # 此处可添加逻辑把one_time_password通过邮件/短信发送给用户 url = device.config_url return Response({"need_verify": True, "config_url": url}, status=status.HTTP_201_CREATED) class TOTPVerifyView(APIView): permission_classes = [] def post(self, request, token, format=None): username = request.data['username'] user = User.objects.get(username=username) if user.is_anonymous: return Response(False, status=status.HTTP_401_UNAUTHORIZED) device = get_user_totp_device(self, user) # 验证时可加drift参数允许1个step的误差,提升兼容性 device_registered = device.verify_token(token, drift=1) if device_registered: if not device.confirmed: device.confirmed = True device.save() # 验证通过,将当前设备加入可信列表 device_fingerprint = get_device_fingerprint(request) TrustedDevice.objects.get_or_create(user=user, fingerprint=device_fingerprint) return Response(True, status=status.HTTP_200_OK) else: return Response({'error': 'Verification code is not valid'}, status=status.HTTP_400_BAD_REQUEST)
2. TOTP有效期延长配置
无需额外修改其他逻辑,只需要在创建TOTPDevice时指定step参数为你需要的时长(单位秒),生成pyotp实例时同步使用device.step作为参数即可,上面代码中已经给出对应配置示例。
内容的提问来源于stack exchange,提问作者fahim irfan
相关产品推荐
相关产品推荐

