如何在Django Rest Framework中实现邮箱与手机号双认证?
DRF 实现邮箱/手机号双登录的最佳方案
方案一:自定义认证后端(推荐,轻量可控)
这是最灵活的实现方式,无需额外依赖,完全自主控制逻辑,适配已有邮箱认证的基础场景。
实现步骤:
- 扩展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'
- 编写自定义认证后端
重写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
- 配置认证后端
在settings.py中注册自定义后端:
AUTHENTICATION_BACKENDS = [ 'your_app_name.backends.EmailOrPhoneBackend', # 保留默认后端(可选,根据需求决定) 'django.contrib.auth.backends.ModelBackend', ]
- 适配登录视图(可选)
如果使用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为例:
- 安装依赖
pip install djangorestframework-simplejwt
- 自定义登录序列化器
重写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
- 配置视图与路由
# 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
相关产品推荐
相关产品推荐

