首页 >后端开发 >Python教程 >在drf项目中实现电话号码验证

在drf项目中实现电话号码验证

Mary-Kate Olsen
Mary-Kate Olsen原创
2024-12-28 10:17:10135浏览

Implémentation de vérification de numéro de téléphone dans un projet drf

要使用 Django REST Framework (DRF) 实现电话号码验证系统,您可以按照以下步骤操作。该系统将允许用户提供他们的电话号码,通过短信接收验证码(例如通过 Twilio),并验证此代码以验证他们的号码。

主要步骤:

  1. 安装必要的依赖项
  2. 编辑用户模板以包含电话号码
  3. 创建模板来存储验证码
  4. 配置短信发送服务(例如 Twilio)
  5. 创建 DRF 序列化器
  6. 创建视图和 API 路由
  7. 管理验证逻辑和安全性

1.安装必要的依赖项

首先,确保您已经安装了必要的库:

  • Django REST Framework:如果您还没有。
  • Twilio:用于发送短信。
  • django-phonenumber-field:用于验证和格式化电话号码。

通过 pip 安装它们:

pip install djangorestframework twilio django-phonenumber-field

将phonenumber_field和rest_framework添加到settings.py中的INSTALLED_APPS中:

# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'phonenumber_field',
    # ...
]

2. 更改用户模板以包含电话号码

如果您使用自定义用户模板,请添加电话号码字段和验证标志。

# models.py

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField

class UserManager(BaseUserManager):
    def create_user(self, email, username, phone_number, password=None):
        if not email:
            raise ValueError('Les utilisateurs doivent avoir une adresse email')
        if not phone_number:
            raise ValueError('Les utilisateurs doivent avoir un numéro de téléphone')

        user = self.model(
            email=self.normalize_email(email),
            username=username,
            phone_number=phone_number,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, phone_number, password=None):
        user = self.create_user(
            email,
            username,
            phone_number,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class CustomUser(AbstractBaseUser):
    email = models.EmailField(verbose_name='adresse email', max_length=255, unique=True)
    username = models.CharField(max_length=50, unique=True)
    phone_number = PhoneNumberField(unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_phone_verified = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username', 'phone_number']

    def __str__(self):
        return self.email

    @property
    def is_staff(self):
        return self.is_admin

注意:如果您已有用户模型,请务必适当添加phone_number 和 is_phone_verified 字段。

3. 创建模板存储验证码

此模板将存储发送给用户的验证码。

# models.py

import random
import string
from django.utils import timezone
from datetime import timedelta

class PhoneVerification(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='phone_verifications')
    code = models.CharField(max_length=6)
    created_at = models.DateTimeField(auto_now_add=True)
    is_verified = models.BooleanField(default=False)

    def is_expired(self):
        return self.created_at < timezone.now() - timedelta(minutes=10)  # Expire après 10 minutes

    def __str__(self):
        return f"Vérification de {self.user.email} - {'Validé' if self.is_verified else 'En attente'}"

4. 配置短信发送服务(例如 Twilio)

您可以使用 Twilio 发送短信。首先创建一个 Twilio 帐户并获取必要的凭据(ACCOUNT_SID、AUTH_TOKEN、FROM_NUMBER)。

将这些配置添加到您的settings.py中:

# settings.py

TWILIO_ACCOUNT_SID = 'votre_account_sid'
TWILIO_AUTH_TOKEN = 'votre_auth_token'
TWILIO_FROM_NUMBER = '+1234567890'  # Numéro Twilio

创建utils.py文件来管理发送短信:

# utils.py

from django.conf import settings
from twilio.rest import Client

def send_sms(to, message):
    client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
    message = client.messages.create(
        body=message,
        from_=settings.TWILIO_FROM_NUMBER,
        to=str(to)
    )
    return message.sid

5. 创建 DRF 序列化器

创建序列化器来处理验证请求和代码验证。

pip install djangorestframework twilio django-phonenumber-field

6. 创建API视图和路由

创建视图来管理验证请求和代码验证。

# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'phonenumber_field',
    # ...
]

注意:您可能需要根据需要调整这些视图,例如您想要在验证过程中创建用户或以不同方式管理现有用户。

7. 配置API路由

在你的urls.py中添加相应的路由。

# models.py

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField

class UserManager(BaseUserManager):
    def create_user(self, email, username, phone_number, password=None):
        if not email:
            raise ValueError('Les utilisateurs doivent avoir une adresse email')
        if not phone_number:
            raise ValueError('Les utilisateurs doivent avoir un numéro de téléphone')

        user = self.model(
            email=self.normalize_email(email),
            username=username,
            phone_number=phone_number,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, phone_number, password=None):
        user = self.create_user(
            email,
            username,
            phone_number,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class CustomUser(AbstractBaseUser):
    email = models.EmailField(verbose_name='adresse email', max_length=255, unique=True)
    username = models.CharField(max_length=50, unique=True)
    phone_number = PhoneNumberField(unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_phone_verified = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username', 'phone_number']

    def __str__(self):
        return self.email

    @property
    def is_staff(self):
        return self.is_admin

8. 添加附加逻辑(可选)

有。为每个用户生成唯一代码

编辑请求视图以将代码与特定用户关联或创建新用户。

b.限制请求数量

为避免滥用,请限制每个用户或电话号码的验证请求数量。

# models.py

import random
import string
from django.utils import timezone
from datetime import timedelta

class PhoneVerification(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='phone_verifications')
    code = models.CharField(max_length=6)
    created_at = models.DateTimeField(auto_now_add=True)
    is_verified = models.BooleanField(default=False)

    def is_expired(self):
        return self.created_at < timezone.now() - timedelta(minutes=10)  # Expire après 10 minutes

    def __str__(self):
        return f"Vérification de {self.user.email} - {'Validé' if self.is_verified else 'En attente'}"

c.验证过程中的用户管理

您可以在验证后决定创建用户或将号码与现有用户关联。

9. 测试和验证

在将系统部署到生产环境之前,请务必在开发环境中测试您的系统。检查:

  • 短信已正确发送。
  • 代码是安全生成和存储的。
  • 支票在设定时间后过期。
  • 错误得到正确处理并传达给用户。

完整的实施示例

为了给您一个概述,这里是受影响文件的完整示例。

模型.py

# settings.py

TWILIO_ACCOUNT_SID = 'votre_account_sid'
TWILIO_AUTH_TOKEN = 'votre_auth_token'
TWILIO_FROM_NUMBER = '+1234567890'  # Numéro Twilio

序列化器.py

# utils.py

from django.conf import settings
from twilio.rest import Client

def send_sms(to, message):
    client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
    message = client.messages.create(
        body=message,
        from_=settings.TWILIO_FROM_NUMBER,
        to=str(to)
    )
    return message.sid

视图.py

# serializers.py

from rest_framework import serializers
from .models import CustomUser, PhoneVerification
from phonenumber_field.serializerfields import PhoneNumberField

class PhoneVerificationRequestSerializer(serializers.Serializer):
    phone_number = PhoneNumberField()

    def validate_phone_number(self, value):
        if CustomUser.objects.filter(phone_number=value).exists():
            raise serializers.ValidationError("Ce numéro de téléphone est déjà utilisé.")
        return value

class PhoneVerificationCodeSerializer(serializers.Serializer):
    phone_number = PhoneNumberField()
    code = serializers.CharField(max_length=6)

    def validate(self, data):
        phone_number = data.get('phone_number')
        code = data.get('code')

        try:
            user = CustomUser.objects.get(phone_number=phone_number)
        except CustomUser.DoesNotExist:
            raise serializers.ValidationError("Utilisateur non trouvé avec ce numéro de téléphone.")

        try:
            verification = PhoneVerification.objects.filter(user=user, code=code, is_verified=False).latest('created_at')
        except PhoneVerification.DoesNotExist:
            raise serializers.ValidationError("Code de vérification invalide.")

        if verification.is_expired():
            raise serializers.ValidationError("Le code de vérification a expiré.")

        data['user'] = user
        data['verification'] = verification
        return data

urls.py

# views.py

from rest_framework import generics, status
from rest_framework.response import Response
from .serializers import PhoneVerificationRequestSerializer, PhoneVerificationCodeSerializer
from .models import CustomUser, PhoneVerification
from .utils import send_sms
import random
import string
from django.utils import timezone
from rest_framework.permissions import AllowAny

class PhoneVerificationRequestView(generics.GenericAPIView):
    serializer_class = PhoneVerificationRequestSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        phone_number = serializer.validated_data['phone_number']

        # Générer un code de 6 chiffres
        code = ''.join(random.choices(string.digits, k=6))

        try:
            user = CustomUser.objects.get(phone_number=phone_number)
            # Si l'utilisateur existe déjà, ne pas permettre la création d'un nouveau
            return Response({"detail": "Ce numéro de téléphone est déjà associé à un utilisateur."}, status=status.HTTP_400_BAD_REQUEST)
        except CustomUser.DoesNotExist:
            pass  # Permettre la création si nécessaire

        # Créer une instance de PhoneVerification
        verification = PhoneVerification.objects.create(user=None, code=code)  # user=None pour l'instant

        # Envoyer le code par SMS
        try:
            send_sms(phone_number, f"Votre code de vérification est : {code}")
        except Exception as e:
            return Response({"detail": "Erreur lors de l'envoi du SMS."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        return Response({"detail": "Code de vérification envoyé."}, status=status.HTTP_200_OK)

class PhoneVerificationCodeView(generics.GenericAPIView):
    serializer_class = PhoneVerificationCodeSerializer
    permission_classes = [AllowAny]

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        verification = serializer.validated_data['verification']

        # Marquer la vérification comme validée
        verification.is_verified = True
        verification.save()

        # Mettre à jour l'utilisateur
        user.is_phone_verified = True
        user.save()

        return Response({"detail": "Numéro de téléphone vérifié avec succès."}, status=status.HTTP_200_OK)

实用程序.py

# urls.py

from django.urls import path
from .views import PhoneVerificationRequestView, PhoneVerificationCodeView

urlpatterns = [
    path('api/verify-phone/request/', PhoneVerificationRequestView.as_view(), name='phone-verification-request'),
    path('api/verify-phone/verify/', PhoneVerificationCodeView.as_view(), name='phone-verification-verify'),
]

10. 安全和优化

  • 限制验证尝试:实施一个系统来限制验证尝试的次数,以避免暴力攻击。

  • 加密代码:为了增加安全性,您可以对数据库中的验证码进行加密。

  • 使用异步任务:要提高性能,请使用异步任务(例如使用 Celery)发送短信,而不会阻塞 API 请求。

  • 配置 HTTPS: 确保您的 API 可通过 HTTPS 访问以确保通信安全。

以上是在drf项目中实现电话号码验证的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn