Python編程使用DRF實現(xiàn)一次性驗證碼OTP
一次性驗證碼,英文是 One Time Password,簡寫為 OTP,又稱動態(tài)密碼或單次有效密碼,是指計算機系統(tǒng)或其他數(shù)字設(shè)備上只能使用一次的密碼,有效期為只有一次登錄會話或很短如 1 分鐘。OTP 避免了一些靜態(tài)密碼認證相關(guān)系的缺點,不容易受到重放攻擊,比如常見的注冊場景,用戶的郵箱或短信會收到一條一次性的激活鏈接,或者收到一次隨機的驗證碼(只能使用一次),從而驗證了郵箱或手機號的有效性。
要實現(xiàn)的功能就是:
1、驗證碼是 6 位的數(shù)字和小寫字母的組合。
2、有效期為 5 分鐘,第二次發(fā)送驗證碼的必須在 1 分鐘之后。
3、如果該郵箱/手機號已經(jīng)注冊,則不能發(fā)送注冊驗證碼。
具體的實現(xiàn)邏輯就是:
1、先生成滿足條件的驗證碼。
2、發(fā)送前驗證,是否上次發(fā)送的驗證碼在 1 分鐘之內(nèi)?是否郵箱已經(jīng)注冊?,如果是,拒絕發(fā)送,并提示用戶,如果否,發(fā)送驗證碼。
3、驗證,是否是 5 分鐘之內(nèi)的驗證碼,是否正確,如果是,則放行。否則提示用戶。
為了驗證驗證碼及其時效,我們需要把發(fā)送驗證碼的時間和對應(yīng)的郵箱記錄下來,那么就需要設(shè)計一張表來存儲。
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 位數(shù)驗證碼,防止破解 :return: """ seeds = "1234567890abcdefghijklmnopqrstuvwxyz" random_str = [] for i in range(6): random_str.append(choice(seeds)) return "".join(random_str)
2、發(fā)送前驗證
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('該郵箱已經(jīng)注冊') # 驗證郵箱號碼合法 if not re.match(EMAIL_REGEX, email): raise serializers.ValidationError('郵箱格式錯誤') # 驗證碼發(fā)送頻率 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('請一分鐘后再次發(fā)送') return email
3、發(fā)送驗證碼
發(fā)送驗證碼,其實就是生成驗證碼并保存的過程,借助于 Django REST framework 框架的 GenericViewSet 和 CreateModelMixin 即可實現(xiàn) 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): """ 發(fā)送驗證碼 """ permission_classes = [AllowAny] #允許所有人注冊 serializer_class = VerifyCodeSerializer #相關(guān)的發(fā)送前驗證邏輯 def generate_code(self): """ 生成6位數(shù)驗證碼 防止破解 :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() 的內(nèi)容 serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) #這一步相當于發(fā)送前驗證 # 從 validated_data 中獲取 mobile email = serializer.validated_data["email"] # 隨機生成code code = self.generate_code() # 發(fā)送短信或郵件驗證碼 sms_status = SendVerifyCode.send_email_code(code=code, to_email_adress=email) if sms_status == 0: # 記錄日志 return Response({"msg": "郵件發(fā)送失敗"}, status=status.HTTP_400_BAD_REQUEST) else: code_record = VerifyCode(code=code, email=email) # 保存驗證碼 code_record.save() return Response( {"msg": f"驗證碼已經(jīng)向 {email} 發(fā)送完成"}, status=status.HTTP_201_CREATED )
SendVerifyCode.send_email_code 的實現(xiàn)如下:
#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 系統(tǒng)驗碼', message=f'您的驗證碼是【[code]】。如非本人操作,請忽略。',from_email='xxxx@163.com',recipient_list = [to_email_adress], fail_silently=False) return success_num except: return 0
4、注冊時驗證
用戶注冊對于數(shù)據(jù)庫來講就是 User 類插入一條記錄,也就是 User 的 view 類的 create 操作來實現(xiàn)注冊。
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": # 如果是創(chuàng)建用戶,那么用 UserRegisterSerializer serializer_class = UserRegisterSerializer else: serializer_class = UserSerializer return serializer_class
這個骨架好了以后,我們現(xiàn)在來編寫 UserRegisterSerializer 類,實現(xiàn)注冊時驗證:
# 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='用戶已經(jīng)存在')]) 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返回到數(shù)據(jù)庫中,只是做驗證 # 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
至此發(fā)送驗證碼的后端編碼已經(jīng)結(jié)束。
最后的話
一次性驗證碼(OTP)的邏輯簡單,需要思考的是如何在 DRF 的框架中填空,填在哪里?這其實需要了解 DRF 的 ModelSerializer 類和 ViewSet 類之前的關(guān)系,在調(diào)用關(guān)系上,ViewSet 類調(diào)用 ModelSerializer 來實現(xiàn)字段的驗證和數(shù)據(jù)保存及序列化,Serializers 類不是必須的,你可以完全自己實現(xiàn)驗證和數(shù)據(jù)保存及序列化,只不過這樣會導(dǎo)致 View 類特別臃腫,不夠優(yōu)雅,不易維護。
參考資料
[1]
Django REST framework: https://www.django-rest-framework.org
以上就是Python編程使用DRF實現(xiàn)一次性驗證碼OTP的詳細內(nèi)容,更多關(guān)于Python編程DRF一次性驗證碼OTP的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python數(shù)學(xué)建模學(xué)習(xí)模擬退火算法多變量函數(shù)優(yōu)化示例解析
模擬退火算法借鑒了統(tǒng)計物理學(xué)的思想,是一種簡單、通用的啟發(fā)式優(yōu)化算法,并在理論上具有概率性全局優(yōu)化性能,因而在科研和工程中得到了廣泛的應(yīng)用2021-10-10用Python 爬取貓眼電影數(shù)據(jù)分析《無名之輩》
這篇文章主要介紹了用Python 爬取貓眼電影數(shù)據(jù)分析《無名之輩》,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Python代碼使用 Pyftpdlib實現(xiàn)FTP服務(wù)器功能
FTP 服務(wù)器,在此之前我都是使用Linux的vsftpd軟件包來搭建FTP服務(wù)器的,現(xiàn)在發(fā)現(xiàn)了利用pyftpdlib可以更加簡單的方法即可實現(xiàn)FTP服務(wù)器的功能 ,需要的朋友可以參考下2019-07-07pandas分組排序 如何獲取第二大的數(shù)據(jù)
這篇文章主要介紹了pandas分組排序 獲取第二大的數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03python 通過pip freeze、dowload打離線包及自動安裝的過程詳解(適用于保密的離線環(huán)境
這篇文章主要介紹了python 通過pip freeze、dowload打離線包及自動安裝【適用于保密的離線環(huán)境】,本文通圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12