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

如何在Django Rest Framework中实现邮箱与手机号双认证?

DRF 实现邮箱/手机号双登录的最佳方案

方案一:自定义认证后端(推荐,轻量可控)

这是最灵活的实现方式,无需额外依赖,完全自主控制逻辑,适配已有邮箱认证的基础场景。

实现步骤:

  1. 扩展User模型(若未添加手机号字段)
    确保用户模型包含唯一的phone字段,可通过继承AbstractUser实现:
# models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    phone = models.CharField(max_length=11, unique=True, blank=True, null=True)
    # 强制邮箱唯一(默认AbstractUser的email非唯一,按需修改)
    email = models.EmailField(unique=True)

在settings.py中配置自定义用户模型:

AUTH_USER_MODEL = 'your_app_name.CustomUser'
  1. 编写自定义认证后端
    重写authenticate方法,判断输入内容为邮箱或手机号,再验证密码:
# backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
import re

User = get_user_model()

class EmailOrPhoneBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        # 正则匹配手机号格式
        if re.match(r'^1[3-9]\d{9}$', username):
            try:
                user = User.objects.get(phone=username)
            except User.DoesNotExist:
                return None
        else:
            # 按原有邮箱逻辑查询用户
            try:
                user = User.objects.get(email=username)
            except User.DoesNotExist:
                return None
        # 验证密码有效性
        if user.check_password(password):
            return user
        return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None
  1. 配置认证后端
    在settings.py中注册自定义后端:
AUTHENTICATION_BACKENDS = [
    'your_app_name.backends.EmailOrPhoneBackend',
    # 保留默认后端(可选,根据需求决定)
    'django.contrib.auth.backends.ModelBackend',
]
  1. 适配登录视图(可选)
    如果使用DRF自带的ObtainAuthToken,可直接复用(后端已兼容邮箱/手机号作为username参数);若需自定义返回字段,可重写视图:
# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomAuthToken(ObtainAuthToken):
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'email': user.email,
            'phone': user.phone
        })

在urls.py中替换默认路由:

urlpatterns = [
    # ...
    path('api-token-auth/', CustomAuthToken.as_view(), name='api_token_auth'),
]

方案二:使用DRF工具包(快速实现完整认证体系)

若不想编写重复代码,可借助成熟工具包快速搭建,同时获得token刷新、注销等附加功能,推荐rest_framework_simplejwt或djoser。

以rest_framework_simplejwt为例:

  1. 安装依赖
pip install djangorestframework-simplejwt
  1. 自定义登录序列化器
    重写TokenObtainPairSerializer的validate方法,支持邮箱/手机号登录:
# serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.contrib.auth import get_user_model
import re
from rest_framework import serializers

User = get_user_model()

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # 添加自定义字段(可选)
        token['email'] = user.email
        token['phone'] = user.phone
        return token

    def validate(self, attrs):
        login_id = attrs.get('username')
        password = attrs.get('password')

        # 判断登录类型
        if re.match(r'^1[3-9]\d{9}$', login_id):
            try:
                user = User.objects.get(phone=login_id)
            except User.DoesNotExist:
                raise serializers.ValidationError('用户不存在')
        else:
            try:
                user = User.objects.get(email=login_id)
            except User.DoesNotExist:
                raise serializers.ValidationError('用户不存在')

        # 验证密码
        if not user.check_password(password):
            raise serializers.ValidationError('密码错误')

        # 生成token数据
        data = {}
        refresh = self.get_token(user)
        data['refresh'] = str(refresh)
        data['access'] = str(refresh.access_token)
        return data
  1. 配置视图与路由
# views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer

class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

在urls.py中添加路由:

urlpatterns = [
    # ...
    path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
]

方案对比

  • 自定义后端:适合需要高度定制、避免额外依赖的场景,代码量少,易维护,与原有邮箱认证逻辑兼容性好。
  • 工具包实现:适合快速搭建完整认证体系(含token刷新、用户管理等功能),若项目需更多认证相关扩展,效率更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 09:16:14