一次性驗證碼,英文是 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 = '[email protected]' ,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