Django中手机号验证码认证最佳方案及基础用户模型登录认证改造方法
基于Django实现手机号+短信验证码的用户认证方案
一、核心流程设计
采用注册登录一体化的极简流程(无需区分新老用户,自动创建新用户):
- 用户提交手机号 → 后端校验手机号格式合法性
- 生成6位随机数字验证码,存入缓存并设置5分钟过期时间
- 调用第三方短信服务商API发送验证码至用户手机
- 用户提交手机号+验证码 → 后端校验验证码有效性
- 验证通过后,若用户不存在则自动创建账号,最终返回认证令牌(如JWT或DRF Token)
二、自定义Django用户模型
替换Django默认依赖username的用户模型,将手机号设为唯一登录标识:
- 在业务app(如
accounts)中定义模型:
# accounts/models.py from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.db import models class UserManager(BaseUserManager): def create_user(self, mobile, password=None, **extra_fields): if not mobile: raise ValueError('必须提供手机号') user = self.model(mobile=mobile, **extra_fields) user.set_unusable_password() # 短信认证无需密码,禁用密码字段 user.save(using=self._db) return user def create_superuser(self, mobile, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) return self.create_user(mobile, password, **extra_fields) class User(AbstractBaseUser, PermissionsMixin): mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号') is_active = models.BooleanField(default=True, verbose_name='是否活跃') is_staff = models.BooleanField(default=False, verbose_name='是否为后台员工') objects = UserManager() USERNAME_FIELD = 'mobile' # 指定手机号为登录标识字段 REQUIRED_FIELDS = [] # 无需额外必填字段 def __str__(self): return self.mobile
- 在
settings.py中配置自定义用户模型:
AUTH_USER_MODEL = 'accounts.User'
三、短信验证码工具实现
对接第三方短信服务商(如阿里云、腾讯云),封装验证码生成、发送、缓存、校验逻辑:
# accounts/utils.py import random import string from django.core.cache import cache def generate_sms_code(length=6): # 生成6位纯数字验证码 return ''.join(random.choices(string.digits, k=length)) def send_sms_code(mobile, code): # 替换为实际短信服务商的API调用逻辑 # 示例:阿里云短信SDK调用需引入对应包并配置密钥 # client = aliyunsdkcore.client.AcsClient(ACCESS_KEY_ID, ACCESS_KEY_SECRET, 'cn-hangzhou') # request = SendSmsRequest.SendSmsRequest() # request.set_PhoneNumbers(mobile) # request.set_SignName('你的短信签名') # request.set_TemplateCode('你的验证码模板ID') # request.set_TemplateParam(f'{{"code": "{code}"}}') # response = client.do_action_with_exception(request) # return response is not None # 开发阶段可打印至控制台替代实际发送 print(f"向手机号{mobile}发送验证码:{code}") return True def cache_sms_code(mobile, code): # 将验证码存入缓存,5分钟过期 cache_key = f'sms_code:{mobile}' cache.set(cache_key, code, timeout=300) def check_sms_code(mobile, code): # 校验验证码有效性,验证通过后删除缓存防止重复使用 cache_key = f'sms_code:{mobile}' cached_code = cache.get(cache_key) if cached_code == code: cache.delete(cache_key) return True return False
四、自定义认证后端
替换Django默认认证逻辑,实现手机号+验证码的认证:
# accounts/backends.py from django.contrib.auth.backends import BaseBackend from .models import User from .utils import check_sms_code class SmsAuthBackend(BaseBackend): def authenticate(self, request, mobile=None, code=None, **kwargs): try: user = User.objects.get(mobile=mobile) except User.DoesNotExist: # 自动创建新用户 user = User.objects.create_user(mobile=mobile) if check_sms_code(mobile, code): return user return None def get_user(self, user_id): try: return User.objects.get(pk=user_id) except User.DoesNotExist: return None
在settings.py中配置认证后端:
AUTHENTICATION_BACKENDS = [ 'accounts.backends.SmsAuthBackend', # 若需保留用户名密码登录,可保留默认后端 # 'django.contrib.auth.backends.ModelBackend', ]
五、编写API视图(基于DRF)
基于Django REST Framework实现发送验证码和登录接口:
# accounts/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.authtoken.models import Token from django.core.cache import cache from django.contrib.auth import authenticate from .utils import generate_sms_code, send_sms_code, cache_sms_code class SendSmsCodeView(APIView): def post(self, request): mobile = request.data.get('mobile') if not mobile or len(mobile) != 11: return Response({'error': '手机号格式错误'}, status=status.HTTP_400_BAD_REQUEST) # 限制发送频率(1分钟内仅允许发送一次) rate_limit_key = f'sms_rate_limit:{mobile}' if cache.get(rate_limit_key): return Response({'error': '请稍后再发送验证码'}, status=status.HTTP_429_TOO_MANY_REQUESTS) code = generate_sms_code() if send_sms_code(mobile, code): cache_sms_code(mobile, code) cache.set(rate_limit_key, '1', timeout=60) return Response({'msg': '验证码发送成功'}, status=status.HTTP_200_OK) else: return Response({'error': '验证码发送失败'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class SmsLoginView(APIView): def post(self, request): mobile = request.data.get('mobile') code = request.data.get('code') if not mobile or not code: return Response({'error': '缺少手机号或验证码'}, status=status.HTTP_400_BAD_REQUEST) user = authenticate(request, mobile=mobile, code=code) if user: token, created = Token.objects.get_or_create(user=user) return Response({'token': token.key, 'mobile': user.mobile}, status=status.HTTP_200_OK) else: return Response({'error': '验证码错误或已过期'}, status=status.HTTP_401_UNAUTHORIZED)
配置URL路由:
# accounts/urls.py from django.urls import path from .views import SendSmsCodeView, SmsLoginView urlpatterns = [ path('send-sms-code/', SendSmsCodeView.as_view(), name='send_sms_code'), path('sms-login/', SmsLoginView.as_view(), name='sms_login'), ]
关键注意事项
- 缓存选型:生产环境建议用Redis替代Django默认内存缓存,避免验证码丢失
- 安全防护:添加短信发送频率限制,防止恶意刷量;验证码验证通过后立即删除缓存,避免重复使用
- 异常处理:在短信发送、缓存操作环节添加异常捕获,避免服务崩溃
- 服务商对接:提前完成短信签名、模板的审核,确保短信正常发送
内容的提问来源于stack exchange,提问作者Gal Naaman
相关产品推荐
相关产品推荐

