一次性验证码,英文是 One Time Password,简写为 OTP,又称动态密码或单次有效密码,是指计算机系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或很短如 1 分钟。OTP 避免了一些静态密码认证相关系的缺点,不容易受到重放攻击,比如常见的注册场景,用户的邮箱或短信会收到一条一次性的激活链接,或者收到一次随机的验证码(只能使用一次),从而验证了邮箱或手机号的有效性。
要实现的功能就是:
1、验证码是 6 位的数字和小写字母的组合。
2、有效期为 5 分钟,第二次发送验证码的必须在 1 分钟之后。
3、如果该邮箱/手机号已经注册,则不能发送注册验证码。
具体的实现逻辑就是:
1、先生成满足条件的验证码。
2、发送前验证,是否上次发送的验证码在 1 分钟之内?是否邮箱已经注册?,如果是,拒绝发送,并提示用户,如果否,发送验证码。
3、验证,是否是 5 分钟之内的验证码,是否正确,如果是,则放行。否则提示用户。
为了验证验证码及其时效,我们需要把发送验证码的时间和对应的邮箱记录下来,那么就需要设计一张表来存储。
1
2
3
4
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、生成验证码
第一个逻辑非常简单,可以直接写出代码:
1
2
3
4
5
6
7
8
9
10
11
|
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 里的每一个字段进行验证,我们直接在里面做填空题即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 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 类,代码都有详细的注释,你很容易就看明白:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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 的实现如下:
1
2
3
4
5
6
7
8
9
10
|
#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 操作来实现注册。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 类,实现注册时验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
# 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的资料请关注服务器之家其它相关文章!
原文链接:https://blog.csdn.net/somenzz/article/details/120072996