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
相关产品推荐
相关产品推荐

