You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django REST Framework注册模块:优化手机号获取用户并重发OTP的实现

问题描述

使用Django REST Framework开发API注册模块时,已实现OTP重发功能,但无法直接获取当前用户。数据库中存储用户手机号、is_active=False、is_verified=False等信息,需要通过手机号定位用户,在OTP过期后重发新OTP。此前尝试在注册时将手机号存入session或缓存来获取用户,但该方案扩展性较差,寻求更优实现方案。

现有RegenerateOTP视图代码
# regenerate OTP
class RegenerateOTP(APIView):
    @swagger_auto_schema(
        operation_summary="This endpoint is responsible for regenerating OTP if expired",
        operation_description="This endpoint regenerate OTP after expiration period",
    )
    def post(self, request):
        instance = CustomUser.objects.filter(is_active=False).first()
        if int(instance.max_otp_try) == 0 and timezone.now() < instance.otp_max_out:
            return Response(
                "Max OTP try reached, try after an hour.",
                status=status.HTTP_400_BAD_REQUEST,
            )
        otp = generateRandomOTP(100000, 999999)
        otp_expire = timezone.now() + timedelta(minutes=10)
        max_otp_try = int(instance.max_otp_try) - 1

        instance.otp = otp
        instance.otp_expire = otp_expire
        instance.max_otp_try = max_otp_try

        if max_otp_try == 0:
            instance.otp_max_out = timezone.now() + datetime.timedelta(hours=1)
        elif max_otp_try == -1:
            instance.max_otp_try = settings.MAX_OTP_TRY
        else:
            instance.otp_max_out = None
            instance.max_otp_try = max_otp_try
        instance.save()
        send_otp(instance.phone_number, otp)
        return Response(
            "successfully re-generated the new otp", status=status.HTTP_200_OK
        )
曾尝试的方案代码
phone_number = request.session.get('phone_number') 
instance = CustomUser.objects.filter(phone_number=phone_number).first()
更优实现方案

方案1:前端提交手机号+辅助验证(推荐)

让前端在请求重发OTP时,将用户手机号作为请求参数提交,同时可搭配图形验证码/滑动验证防止恶意请求。后端通过手机号精准定位未激活用户,避免session/cache的局限性。

修改后的视图代码:

from rest_framework import serializers

class RegenerateOTPSerializer(serializers.Serializer):
    phone_number = serializers.CharField(required=True)
    # 可选:添加图形验证码字段
    # captcha = serializers.CharField(required=True)

class RegenerateOTP(APIView):
    @swagger_auto_schema(
        operation_summary="重发过期OTP",
        operation_description="OTP过期后,通过手机号重新生成并发送OTP",
        request_body=RegenerateOTPSerializer
    )
    def post(self, request):
        serializer = RegenerateOTPSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        phone_number = serializer.validated_data['phone_number']
        
        # 定位未激活且未验证的用户
        instance = CustomUser.objects.filter(
            phone_number=phone_number,
            is_active=False,
            is_verified=False
        ).first()
        
        if not instance:
            return Response(
                "未找到待验证的用户",
                status=status.HTTP_404_NOT_FOUND
            )
        
        # 检查OTP尝试次数限制
        if instance.max_otp_try == 0 and timezone.now() < instance.otp_max_out:
            return Response(
                "已达到最大OTP尝试次数,请1小时后再试",
                status=status.HTTP_400_BAD_REQUEST
            )
        
        # 生成新OTP并更新用户信息
        otp = generateRandomOTP(100000, 999999)
        otp_expire = timezone.now() + timedelta(minutes=10)
        max_otp_try = instance.max_otp_try - 1

        instance.otp = otp
        instance.otp_expire = otp_expire
        instance.max_otp_try = max_otp_try

        if max_otp_try == 0:
            instance.otp_max_out = timezone.now() + datetime.timedelta(hours=1)
        elif max_otp_try == -1:
            instance.max_otp_try = settings.MAX_OTP_TRY
            instance.otp_max_out = None
        else:
            instance.otp_max_out = None
        
        instance.save()
        send_otp(instance.phone_number, otp)
        return Response(
            "OTP已重新发送", status=status.HTTP_200_OK
        )

方案2:使用临时注册令牌

用户首次请求注册并获取OTP时,后端返回一个包含手机号的临时JWT令牌(有效期与OTP过期时间一致)。重发OTP时,前端携带该令牌,后端解析令牌获取手机号,进而定位用户。

示例代码(令牌生成与解析):

import jwt
from datetime import datetime, timedelta
from django.conf import settings

# 注册成功后生成临时令牌
def generate_registration_token(phone_number):
    payload = {
        'phone_number': phone_number,
        'exp': datetime.utcnow() + timedelta(minutes=10)  # 与OTP有效期一致
    }
    token = jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256')
    return token

# 重发OTP时解析令牌
def parse_registration_token(token):
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
        return payload['phone_number']
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

# 修改RegenerateOTP视图
class RegenerateOTP(APIView):
    def post(self, request):
        token = request.headers.get('X-Registration-Token')
        if not token:
            return Response("缺少注册令牌", status=status.HTTP_400_BAD_REQUEST)
        
        phone_number = parse_registration_token(token)
        if not phone_number:
            return Response("令牌无效或已过期", status=status.HTTP_400_BAD_REQUEST)
        
        # 后续逻辑同方案1,通过phone_number查找用户...

方案3:手机号+设备标识定位

结合用户手机号与设备标识(如User-Agent哈希、IP地址哈希)来定位用户,减少恶意用户冒充他人请求重发OTP的风险。

示例代码:

import hashlib

def get_device_fingerprint(request):
    # 生成简单的设备指纹
    user_agent = request.META.get('HTTP_USER_AGENT', '')
    ip_address = request.META.get('REMOTE_ADDR', '')
    fingerprint = hashlib.md5(f"{user_agent}{ip_address}".encode()).hexdigest()
    return fingerprint

# 修改RegenerateOTP视图
class RegenerateOTP(APIView):
    def post(self, request):
        serializer = RegenerateOTPSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        phone_number = serializer.validated_data['phone_number']
        device_fingerprint = get_device_fingerprint(request)
        
        instance = CustomUser.objects.filter(
            phone_number=phone_number,
            is_active=False,
            is_verified=False,
            device_fingerprint=device_fingerprint  # 需在CustomUser模型添加该字段
        ).first()
        
        # 后续逻辑同方案1...

内容的提问来源于stack exchange,提问作者Dennis Akagha

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 00:40:07