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

Django中手机号验证码认证最佳方案及基础用户模型登录认证改造方法

基于Django实现手机号+短信验证码的用户认证方案

一、核心流程设计

采用注册登录一体化的极简流程(无需区分新老用户,自动创建新用户):

  • 用户提交手机号 → 后端校验手机号格式合法性
  • 生成6位随机数字验证码,存入缓存并设置5分钟过期时间
  • 调用第三方短信服务商API发送验证码至用户手机
  • 用户提交手机号+验证码 → 后端校验验证码有效性
  • 验证通过后,若用户不存在则自动创建账号,最终返回认证令牌(如JWT或DRF Token)

二、自定义Django用户模型

替换Django默认依赖username的用户模型,将手机号设为唯一登录标识:

  1. 在业务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
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 06:23:19