You need to enable JavaScript to run this app. Django two-factor-auth多设备登录验证及延长TOTP码有效期问题
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 01:51:03