Django中手机号OTP验证后生成Token的认证方案咨询
问题描述
我正在Django中开发一个应用,该应用可从API服务器发送OTP且功能正常。现需实现:用户验证手机号成功后为其生成Token,后续用户发起请求时需在请求头中携带该Token进行认证。
此前我尝试使用Django REST Framework的TokenAuthentication,但该方式需要用户通过用户名密码完成认证,而我无法向Django服务器传递用户名密码,因此咨询如何通过手机号+OTP的方式实现上述需求。
项目配置文件 settings.py
""" Django settings for backend project. Generated by 'django-admin startproject' using Django 3.2.14. For more information on this file, see https://docs.djangoproject.com/en/3.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.2/ref/settings/ """ from pathlib import Path # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [ "2858-151-240-216-221.ngrok-free.app", "localhost" ] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', 'home', 'requests', 'users', 'api', 'otp_api' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'backend.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / "templates"], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'backend.wsgi.application' # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.2/howto/static-files/ STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / "static" ] MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'uploads' # Default primary key field type # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["rest_framework.authentication.TokenAuthentication"], "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], }
发送OTP的API视图 whatsappAPI
import random from django.http import HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from .serializer import ProfileSerializer from users.models import * from utils.mixins import send_otp_on_phone class whatsappAPI(APIView): serializer_class = ProfileSerializer authentication_classes = [] permission_classes = [] def post(self, request): serializer_obj = ProfileSerializer(data=request.data) if serializer_obj.is_valid(): phone_number = serializer_obj.data.get("phone_number") Profile.objects.create(phone_number=phone_number) send_otp_on_phone(phone_number=phone_number) user_profile = Profile.objects.filter(phone_number=request.data["phone_number"]).values()[0] return Response(user_profile)
OTP验证API视图 OTPApi
import random from django.http import Http404 from rest_framework.views import APIView from rest_framework.response import Response from .serializer import OTPSerializer from .models import * from utils.mixins import * class OTPApi(APIView): serializer_class = OTPSerializer authentication_classes = [] permission_classes = [] def post(self, request): serializer_obj = OTPSerializer(data=request.data) if serializer_obj.is_valid(): otp = serializer_obj.data.get("otp") phone_number = serializer_obj.data.get("phone_number") code_verification = check_code(otp=otp, phone_number=phone_number) if code_verification: OTPModel.objects.create(otp=otp, phone_number=phone_number) user_profile = OTPModel.objects.filter(otp=otp).values()[0] # 我想在这里生成Token return Response(user_profile) else: return Http404("invalid otp")
解决方案
要实现手机号+OTP生成Token的认证流程,可基于Django默认User模型结合DRF的TokenAuthentication完成,步骤如下:
1. 在OTP验证成功时生成Token
直接修改OTPApi的post方法,添加用户创建与Token生成逻辑:
from django.contrib.auth.models import User from rest_framework.authtoken.models import Token # ... 原有代码 ... if code_verification: # 用手机号作为用户名,自动创建或获取User实例(生成随机密码,无需使用) user, created = User.objects.get_or_create( username=phone_number, defaults={'password': User.objects.make_random_password()} ) # 生成或获取该用户对应的Token token, created = Token.objects.get_or_create(user=user) # 将Token加入返回数据 user_profile['token'] = token.key return Response(user_profile)
2. 后续请求认证
用户发起后续请求时,在请求头中携带Authorization: Token <生成的Token值>,DRF的TokenAuthentication会自动验证Token有效性,通过后即可访问需要认证的接口。
3. 优化建议
- 给
Profile模型添加与User的外键关联,方便后续关联用户扩展信息; - OTP验证成功后,删除或标记已使用的OTP记录,避免重复验证;
- 若业务需要,可自定义User模型,将手机号设为唯一标识字段,替代默认用户名。
内容的提问来源于stack exchange,提问作者Amirhosein Pourmoshir
相关产品推荐
相关产品推荐

