Python编程使用DRF实现一次性验证码OTP

一次性验证码,英文是 One Time Password,简写为 OTP,又称动态密码或单次有效密码,是指计算机系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或很短如 1 分钟。OTP 避免了一些静态密码认证相关系的缺点,不容易受到重放攻击,比如常见的注册场景,用户的邮箱或短信会收到一条一次性的激活链接,或者收到一次随机的验证码(只能使用一次),从而验证了邮箱或手机号的有效性。

要实现的功能就是:

1、验证码是 6 位的数字和小写字母的组合。

2、有效期为 5 分钟,第二次发送验证码的必须在 1 分钟之后。

3、如果该邮箱/手机号已经注册,则不能发送注册验证码。

具体的实现逻辑就是:

1、先生成满足条件的验证码。

2、发送前验证,是否上次发送的验证码在 1 分钟之内?是否邮箱已经注册?,如果是,拒绝发送,并提示用户,如果否,发送验证码。

3、验证,是否是 5 分钟之内的验证码,是否正确,如果是,则放行。否则提示用户。

为了验证验证码及其时效,我们需要把发送验证码的时间和对应的邮箱记录下来,那么就需要设计一张表来存储。

class VerifyCode(models.Model):
    mobile = models.CharField(max_length=11, verbose_name="手机号", blank=True)
    email = models.EmailField(verbose_name="email", blank=True)
    code = models.CharField(max_length=8, verbose_name="验证码")
    add_time = models.DateTimeField(verbose_name='生成时间', auto_now_add=True)

1、生成验证码

第一个逻辑非常简单,可以直接写出代码:

from random import choice
 def generate_code(self):
 """
 生成 6 位数验证码,防止破解
 :return:
 """
 seeds = "1234567890abcdefghijklmnopqrstuvwxyz"
 random_str = []
 for i in range(6):
  random_str.append(choice(seeds))
 return "".join(random_str)

2、发送前验证

Django REST framework 框架的 Serializer 可以对 Models 里的每一个字段进行验证,我们直接在里面做填空题即可:

# serializers.py
class VerifyCodeSerializer(serializers.Serializer):
    email = serializers.EmailField(required=True)
    def validate_email(self, email):
        """
        验证邮箱是否合法
        """
        # 邮箱是否注册
        if User.objects.filter(email = email).count():
            raise serializers.ValidationError('该邮箱已经注册')
         # 验证邮箱号码合法
        if not re.match(EMAIL_REGEX, email):
            raise serializers.ValidationError('邮箱格式错误')
         # 验证码发送频率
        one_minute_age = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_minute_age, email=email).count():
            raise serializers.ValidationError('请一分钟后再次发送')
        return email
 

3、发送验证码

发送验证码,其实就是生成验证码并保存的过程,借助于 Django REST framework 框架的 GenericViewSet 和 CreateModelMixin 即可实现 view 类,代码都有详细的注释,你很容易就看明白:

from rest_framework.response import Response
from rest_framework.views import status
from rest_framework import mixins, viewsets
class VerifyCodeViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin):
    """
    发送验证码
    """
    permission_classes = [AllowAny] #允许所有人注册
    serializer_class = VerifyCodeSerializer #相关的发送前验证逻辑
    def generate_code(self):
        """
        生成6位数验证码 防止破解
        :return:
        """
        seeds = "1234567890abcdefghijklmnopqrstuvwxyz"
        random_str = []
        for i in range(6):
            random_str.append(choice(seeds))
        return "".join(random_str)
    def create(self, request, *args, **kwargs):
  # 自定义的 create() 的内容
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True) #这一步相当于发送前验证
        # 从 validated_data 中获取 mobile
        email = serializer.validated_data["email"]
        # 随机生成code
        code = self.generate_code()
        # 发送短信或邮件验证码
        sms_status = SendVerifyCode.send_email_code(code=code, to_email_adress=email)
         if sms_status == 0:
   # 记录日志

            return Response({"msg": "邮件发送失败"}, status=status.HTTP_400_BAD_REQUEST)
        else:
            code_record = VerifyCode(code=code, email=email)
            # 保存验证码
            code_record.save()
            return Response(
                {"msg": f"验证码已经向 {email} 发送完成"}, status=status.HTTP_201_CREATED
            )
 

SendVerifyCode.send_email_code 的实现如下:

#encoding=utf-8
from django.core.mail import send_mail
class SendVerifyCode(object):
    @staticmethod
    def send_email_code(code,to_email_adress):
        try:
            success_num = send_mail(subject='xxx 系统验码', message=f'您的验证码是【[code]】。如非本人操作,请忽略。',from_email='xxxx@163.com',recipient_list = [to_email_adress], fail_silently=False)
            return success_num
        except:
            return 0

4、注册时验证

用户注册对于数据库来讲就是 User 类插入一条记录,也就是 User 的 view 类的 create 操作来实现注册。

from .serializers import UserRegisterSerializer, UserSerializer
class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = UserSerializer

    def get_serializer_class(self):
        if self.action == "create":
            # 如果是创建用户,那么用 UserRegisterSerializer
            serializer_class = UserRegisterSerializer
        else:
            serializer_class = UserSerializer
        return serializer_class

这个骨架好了以后,我们现在来编写 UserRegisterSerializer 类,实现注册时验证:

# serializers.py
class UserRegisterSerializer(serializers.ModelSerializer):
    # error_message:自定义错误消息提示的格式
    code = serializers.CharField(required=True, allow_blank=False, min_length=6, max_length=6, help_text='验证码',
                                 error_messages={
                                     'blank': '请输入验证码',
                                     'required': '请输入验证码',
                                     'min_length': '验证码格式错误',
                                     'max_length': '验证码格式错误',
                                 }, write_only=True)
    # 利用drf中的validators验证username是否唯一
    username = serializers.CharField(required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(), message='用户已经存在')])
    email = serializers.EmailField(required=True, allow_blank=False,
                                   validators=[UniqueValidator(queryset=User.objects.all(), message='邮箱已被注册')])
     # 对code字段单独验证(validate_+字段名)
    def validate_code(self, code):
        verify_records = VerifyCode.objects.filter(email=self.initial_data['email']).order_by('-add_time')
        if verify_records:
            last_record = verify_records[0]
            # 判断验证码是否过期
            five_minutes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)  # 获取5分钟之前的时间
            if last_record.add_time < five_minutes_ago:
                raise serializers.ValidationError('验证码过期')
            # 判断验证码是否正确
            if last_record.code != code:
                raise serializers.ValidationError('验证码错误')
            # 不用将code返回到数据库中,只是做验证
            # return code
        else:
            raise serializers.ValidationError('验证码不存在')
    # attrs:每个字段validate之后总的dict
    def validate(self, attrs):
        # attrs['mobile'] = attrs['username']
        # 从attrs中删除code字段
        del attrs['code']
        return attrs
    class Meta:
        model = User
        fields = ('username', 'email', 'password', 'code')
        extra_kwargs = {'password': {'write_only': True}}
    def create(self, validated_data):
        user = User(
            email=validated_data['email'],
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

至此发送验证码的后端编码已经结束。

最后的话

一次性验证码(OTP)的逻辑简单,需要思考的是如何在 DRF 的框架中填空,填在哪里?这其实需要了解 DRF 的 ModelSerializer 类和 ViewSet 类之前的关系,在调用关系上,ViewSet 类调用 ModelSerializer 来实现字段的验证和数据保存及序列化,Serializers 类不是必须的,你可以完全自己实现验证和数据保存及序列化,只不过这样会导致 View 类特别臃肿,不够优雅,不易维护。

参考资料

[1]

Django REST framework: https://www.django-rest-framework.org

以上就是Python编程使用DRF实现一次性验证码OTP的详细内容,更多关于Python编程DRF一次性验证码OTP的资料请关注我们其它相关文章!

(0)

相关推荐

  • Django Rest framework认证组件详细用法

    本文详细讲述了DRF认证组件的原理以及用法. 源码剖析 讲解DRF版本的时候我们都知道了,在dispatch方法里执行了initial方法来初始化我们的版本. 而在initial方法里有我们的认证.权限.频率组件,如下图: 我们再点进去认证组件看看: 可以看到,我们的权限组件返回的是request.user,那么这里返回的request是新的还是旧的呢? initial方法是在request重新赋值之后执行的,所以我们这里的request是新的--也就是Request类的实例化对象. 所以,这个

  • Django Rest framework之认证的实现代码

    django rest framework 官网 在学习django rest framework(下面简称drf)之前需要知道 对RESTful API设计有一定了解 对django框架有一定认识,本身drf就是基于django做的 对python面向对象编程有了解(drf会对一些原生的django类做封装) 一.前言 在学习drf之前的时候,先简单说一下需要的预备知识.在django中,路由匹配之后,会进行路由分发,这个时候会有两种选择模式的选择.也就是FBV与CBV. 1.FBV fbv就

  • python django 实现验证码的功能实例代码

    我也是刚学Python  Django不久很多都不懂,所以我现在想一边学习一边记录下来然后大家一起讨论! 验证码功能一开始我在网上找了很多的demo但是我在模仿他们写的时候,发现在我的版本上根本就不能运行起来在前端页面显示的时候是图裂,有可能是我用的Python3.5的版本和django是1.10的版本的原因,我看了晚上很多的版本都是2.7的,所以我问了很多前辈和大神,终于发现了原因的所在,好了代码我就在下面帖粗来了. 这是我的项目目录. 验证码要成功显示就必须要有一个验证码生成器,所以就要写一

  • django rest framework 实现用户登录认证详解

    1.安装 pip install djangorestframework 2.创建项目及应用 创建项目 创建应用 目录结构如图 3.设置settings.py 设置数据库连接 # MySQL 增加mysql 连接 DATABASES = { 'default':{ 'ENGINE':'django.db.backends.mysql', 'HOST':'127.0.0.1', 'PORT':'3306', 'NAME':'dbname', # 数据库名 'USER':'username', 'P

  • 深度解析Django REST Framework 批量操作

    我们都知道Django rest framework这个库,默认只支持批量查看,不支持批量更新(局部或整体)和批量删除. 下面我们来讨论这个问题,看看如何实现批量更新和删除操作. DRF基本情况 我们以下面的代码作为例子: models: from django.db import models # Create your models here. class Classroom(models.Model): location = models.CharField(max_length=128)

  • Python编程使用DRF实现一次性验证码OTP

    一次性验证码,英文是 One Time Password,简写为 OTP,又称动态密码或单次有效密码,是指计算机系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或很短如 1 分钟.OTP 避免了一些静态密码认证相关系的缺点,不容易受到重放攻击,比如常见的注册场景,用户的邮箱或短信会收到一条一次性的激活链接,或者收到一次随机的验证码(只能使用一次),从而验证了邮箱或手机号的有效性. 要实现的功能就是: 1.验证码是 6 位的数字和小写字母的组合. 2.有效期为 5 分钟,第二次发送

  • Python编程实现的图片识别功能示例

    本文实例讲述了Python编程实现的图片识别功能.分享给大家供大家参考,具体如下: 1. 安装PIL,官方没有WIN64位,Pillow替代 pip install Pillow-2.7.0-cp27-none-win_amd64.whl 2. 安装Pytesser 下载pytesser_v0.0.1.zip,解压后复制进Python27\Lib\site-packges\pytesser路径下,无pytesser则新建 在Python27\Lib\site-packges\pytesser中新

  • python编程使用selenium模拟登陆淘宝实例代码

    selenium简介 selenium 是一个web的自动化测试工具,不少学习功能自动化的同学开始首选selenium ,相因为它相比QTP有诸多有点: * 免费,也不用再为破解QTP而大伤脑筋 * 小巧,对于不同的语言它只是一个包而已,而QTP需要下载安装1个多G 的程序. * 这也是最重要的一点,不管你以前更熟悉C. java.ruby.python.或都是C# ,你都可以通过selenium完成自动化测试,而QTP只支持VBS * 支持多平台:windows.linux.MAC ,支持多浏

  • 3个 Python 编程技巧

    目录 1.如何按照字典的值的大小进行排序 2.优雅的一次性判断多个条件 3.如何优雅的合并两个字典 今天分享 3 个 Python 编程小技巧,来看看你是否用过? 1.如何按照字典的值的大小进行排序 我们知道,字典的本质是哈希表,本身是无法排序的,但 Python 3.6 之后,字典是可以按照插入的顺序进行遍历的,这就是有序字典,其中的原理,可以阅读为什么 Python3.6 之后字典是有序的. 知道了这一点,就好办了,先把字典的键值对列表排序,然后重新插入新的字典,这样新字典就可以按照值的大小

  • vue+drf+第三方滑动验证码接入的实现

    目录 1.背景 2.验证流程 3.创建验证 4.前端代码 4.1 添加核心js文件 4.2 添加配置 4.3 组件修改 5.后端代码 5.1 添加配置 5.2 接收验证并返回 5.3 添加url路由 6.运行测试 1.背景 近期在项目开发练习中用到了登录功能 + 验证码的需求,验证码一般分为三种类型:图片验证码.短信验证码.滑动验证码,相关实现思路如下 图片验证码 对于图片验证码的实现可以借助python中的第三方模块pillow的相关方法进行实现(有时间会写文章) 短信验证码 短信验证码的主要

  • 3 个超有用的 Python 编程小技巧

    目录 1.如何按照字典的值的大小进行排序 2.优雅的一次性判断多个条件 3.如何优雅的合并两个字典 1.如何按照字典的值的大小进行排序 我们知道,字典的本质是哈希表,本身是无法排序的,但 Python 3.6 之后,字典是可以按照插入的顺序进行遍历的,这就是有序字典,其中的原理,可以阅读 Python3.6 之后字典是有序的? . 知道了这一点,就好办了,先把字典的键值对列表排序,然后重新插入新的字典,这样新字典就可以按照值的大小进行遍历输出. 代码如下: >>> xs = {'a':

  • Python编程django实现同一个ip十分钟内只能注册一次

    很多小伙伴都会有这样的问题,说一个ip地址十分钟内之内注册一次,用来防止用户来重复注册带来不必要的麻烦 逻辑: 取ip,在数据库找ip是否存在,存在判断当前时间和ip上次访问时间之差,小于600不能注册,到登录界面,大于600可以注册,设计一个数据库来存储这个ip地址和访问时间, class Ip(models.Model): ip=models.CharField(max_length=20) time=models.DateTimeField() class Meta: verbose_na

  • python编程羊车门问题代码示例

    问题: 有3扇关闭的门,一扇门后面停着汽车,其余门后是山羊,只有主持人知道每扇门后面是什么.参赛者可以选择一扇门,在开启它之前,主持人会开启另外一扇门,露出门后的山羊,然后允许参赛者更换自己的选择. 请问: 1.按照你的第一感觉回答,你觉得不换选择能有更高的几率获得汽车,还是换选择能有更高的几率获得汽车?或几率没有发生变化? 答:第一感觉换与不换获奖几率没有发生变化. 2.请自己认真分析一下"不换选择能有更高的几率获得汽车,还是换选择能有更高的几率获得汽车?或几率没有发生变化?" 写出

  • Python编程实现的简单Web服务器示例

    本文实例讲述了Python编程实现的简单Web服务器.分享给大家供大家参考,具体如下: 最近有个需求,就是要创建一个简到要多简单就有多简单的web服务器,目的就是需要一个后台进程用来接收请求然后处理并返回结果,因此就想到了使用Python来实现. 首先创建一个myapp.py文件,其中定义了一个方法,所有的请求都会经过此方法,可以在此方法里处理传递的url和参数,并返回结果. def myapp(environ, start_response): status = '200 OK' header

  • Python编程实现输入某年某月某日计算出这一天是该年第几天的方法

    本文实例讲述了Python编程实现输入某年某月某日计算出这一天是该年第几天的方法.分享给大家供大家参考,具体如下: #基于 Python3 一种做法: def is_leap_year(year): # 判断闰年,是则返回True,否则返回False if (year % 4 == 0 and year % 100 != 0) or year % 400 == 0: return True else: return False def function1(year, month, day): #

随机推荐