Python編程使用DRF實(shí)現(xiàn)一次性驗(yàn)證碼OTP
一次性驗(yàn)證碼,英文是 One Time Password,簡(jiǎn)寫(xiě)為 OTP,又稱(chēng)動(dòng)態(tài)密碼或單次有效密碼,是指計(jì)算機(jī)系統(tǒng)或其他數(shù)字設(shè)備上只能使用一次的密碼,有效期為只有一次登錄會(huì)話(huà)或很短如 1 分鐘。OTP 避免了一些靜態(tài)密碼認(rèn)證相關(guān)系的缺點(diǎn),不容易受到重放攻擊,比如常見(jiàn)的注冊(cè)場(chǎng)景,用戶(hù)的郵箱或短信會(huì)收到一條一次性的激活鏈接,或者收到一次隨機(jī)的驗(yàn)證碼(只能使用一次),從而驗(yàn)證了郵箱或手機(jī)號(hào)的有效性。
要實(shí)現(xiàn)的功能就是:
1、驗(yàn)證碼是 6 位的數(shù)字和小寫(xiě)字母的組合。
2、有效期為 5 分鐘,第二次發(fā)送驗(yàn)證碼的必須在 1 分鐘之后。
3、如果該郵箱/手機(jī)號(hào)已經(jīng)注冊(cè),則不能發(fā)送注冊(cè)驗(yàn)證碼。
具體的實(shí)現(xiàn)邏輯就是:
1、先生成滿(mǎn)足條件的驗(yàn)證碼。
2、發(fā)送前驗(yàn)證,是否上次發(fā)送的驗(yàn)證碼在 1 分鐘之內(nèi)?是否郵箱已經(jīng)注冊(cè)?,如果是,拒絕發(fā)送,并提示用戶(hù),如果否,發(fā)送驗(yàn)證碼。
3、驗(yàn)證,是否是 5 分鐘之內(nèi)的驗(yàn)證碼,是否正確,如果是,則放行。否則提示用戶(hù)。
為了驗(yàn)證驗(yàn)證碼及其時(shí)效,我們需要把發(fā)送驗(yàn)證碼的時(shí)間和對(duì)應(yīng)的郵箱記錄下來(lái),那么就需要設(shè)計(jì)一張表來(lái)存儲(chǔ)。
class VerifyCode(models.Model):
mobile = models.CharField(max_length=11, verbose_name="手機(jī)號(hào)", blank=True)
email = models.EmailField(verbose_name="email", blank=True)
code = models.CharField(max_length=8, verbose_name="驗(yàn)證碼")
add_time = models.DateTimeField(verbose_name='生成時(shí)間', auto_now_add=True)
1、生成驗(yàn)證碼
第一個(gè)邏輯非常簡(jiǎn)單,可以直接寫(xiě)出代碼:
from random import choice def generate_code(self): """ 生成 6 位數(shù)驗(yàn)證碼,防止破解 :return: """ seeds = "1234567890abcdefghijklmnopqrstuvwxyz" random_str = [] for i in range(6): random_str.append(choice(seeds)) return "".join(random_str)
2、發(fā)送前驗(yàn)證
Django REST framework 框架的 Serializer 可以對(duì) Models 里的每一個(gè)字段進(jìn)行驗(yàn)證,我們直接在里面做填空題即可:
# serializers.py
class VerifyCodeSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
def validate_email(self, email):
"""
驗(yàn)證郵箱是否合法
"""
# 郵箱是否注冊(cè)
if User.objects.filter(email = email).count():
raise serializers.ValidationError('該郵箱已經(jīng)注冊(cè)')
# 驗(yàn)證郵箱號(hào)碼合法
if not re.match(EMAIL_REGEX, email):
raise serializers.ValidationError('郵箱格式錯(cuò)誤')
# 驗(yàn)證碼發(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('請(qǐng)一分鐘后再次發(fā)送')
return email
3、發(fā)送驗(yàn)證碼
發(fā)送驗(yàn)證碼,其實(shí)就是生成驗(yàn)證碼并保存的過(guò)程,借助于 Django REST framework 框架的 GenericViewSet 和 CreateModelMixin 即可實(shí)現(xiàn) view 類(lèi),代碼都有詳細(xì)的注釋?zhuān)愫苋菀拙涂疵靼祝?/p>
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ā)送驗(yàn)證碼
"""
permission_classes = [AllowAny] #允許所有人注冊(cè)
serializer_class = VerifyCodeSerializer #相關(guān)的發(fā)送前驗(yàn)證邏輯
def generate_code(self):
"""
生成6位數(shù)驗(yàn)證碼 防止破解
: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) #這一步相當(dāng)于發(fā)送前驗(yàn)證
# 從 validated_data 中獲取 mobile
email = serializer.validated_data["email"]
# 隨機(jī)生成code
code = self.generate_code()
# 發(fā)送短信或郵件驗(yàn)證碼
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)
# 保存驗(yàn)證碼
code_record.save()
return Response(
{"msg": f"驗(yàn)證碼已經(jīng)向 {email} 發(fā)送完成"}, status=status.HTTP_201_CREATED
)
SendVerifyCode.send_email_code 的實(shí)現(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)驗(yàn)碼', message=f'您的驗(yàn)證碼是【[code]】。如非本人操作,請(qǐng)忽略。',from_email='xxxx@163.com',recipient_list = [to_email_adress], fail_silently=False)
return success_num
except:
return 0
4、注冊(cè)時(shí)驗(yàn)證
用戶(hù)注冊(cè)對(duì)于數(shù)據(jù)庫(kù)來(lái)講就是 User 類(lèi)插入一條記錄,也就是 User 的 view 類(lèi)的 create 操作來(lái)實(shí)現(xiàn)注冊(cè)。
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)建用戶(hù),那么用 UserRegisterSerializer
serializer_class = UserRegisterSerializer
else:
serializer_class = UserSerializer
return serializer_class
這個(gè)骨架好了以后,我們現(xiàn)在來(lái)編寫(xiě) UserRegisterSerializer 類(lèi),實(shí)現(xiàn)注冊(cè)時(shí)驗(yàn)證:
# serializers.py
class UserRegisterSerializer(serializers.ModelSerializer):
# error_message:自定義錯(cuò)誤消息提示的格式
code = serializers.CharField(required=True, allow_blank=False, min_length=6, max_length=6, help_text='驗(yàn)證碼',
error_messages={
'blank': '請(qǐng)輸入驗(yàn)證碼',
'required': '請(qǐng)輸入驗(yàn)證碼',
'min_length': '驗(yàn)證碼格式錯(cuò)誤',
'max_length': '驗(yàn)證碼格式錯(cuò)誤',
}, write_only=True)
# 利用drf中的validators驗(yàn)證username是否唯一
username = serializers.CharField(required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message='用戶(hù)已經(jīng)存在')])
email = serializers.EmailField(required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message='郵箱已被注冊(cè)')])
# 對(duì)code字段單獨(dú)驗(yàn)證(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]
# 判斷驗(yàn)證碼是否過(guò)期
five_minutes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) # 獲取5分鐘之前的時(shí)間
if last_record.add_time < five_minutes_ago:
raise serializers.ValidationError('驗(yàn)證碼過(guò)期')
# 判斷驗(yàn)證碼是否正確
if last_record.code != code:
raise serializers.ValidationError('驗(yàn)證碼錯(cuò)誤')
# 不用將code返回到數(shù)據(jù)庫(kù)中,只是做驗(yàn)證
# return code
else:
raise serializers.ValidationError('驗(yàn)證碼不存在')
# attrs:每個(gè)字段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ā)送驗(yàn)證碼的后端編碼已經(jīng)結(jié)束。
最后的話(huà)
一次性驗(yàn)證碼(OTP)的邏輯簡(jiǎn)單,需要思考的是如何在 DRF 的框架中填空,填在哪里?這其實(shí)需要了解 DRF 的 ModelSerializer 類(lèi)和 ViewSet 類(lèi)之前的關(guān)系,在調(diào)用關(guān)系上,ViewSet 類(lèi)調(diào)用 ModelSerializer 來(lái)實(shí)現(xiàn)字段的驗(yàn)證和數(shù)據(jù)保存及序列化,Serializers 類(lèi)不是必須的,你可以完全自己實(shí)現(xiàn)驗(yàn)證和數(shù)據(jù)保存及序列化,只不過(guò)這樣會(huì)導(dǎo)致 View 類(lèi)特別臃腫,不夠優(yōu)雅,不易維護(hù)。
參考資料
[1]
Django REST framework: https://www.django-rest-framework.org
以上就是Python編程使用DRF實(shí)現(xiàn)一次性驗(yàn)證碼OTP的詳細(xì)內(nèi)容,更多關(guān)于Python編程DRF一次性驗(yàn)證碼OTP的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Python+selenium破解拼圖驗(yàn)證碼的腳本
- 利用Python生成隨機(jī)驗(yàn)證碼詳解
- Python實(shí)現(xiàn)隨機(jī)生成圖片驗(yàn)證碼詳解
- Python+Selenium+Pytesseract實(shí)現(xiàn)圖片驗(yàn)證碼識(shí)別
- Python實(shí)現(xiàn)新版正方系統(tǒng)滑動(dòng)驗(yàn)證碼識(shí)別
- python通過(guò)pillow識(shí)別動(dòng)態(tài)驗(yàn)證碼的示例代碼
- python調(diào)用文字識(shí)別OCR輕松搞定驗(yàn)證碼
- 基于Python實(shí)現(xiàn)原生的登錄驗(yàn)證碼詳情
- 詳解Python結(jié)合Genetic?Algorithm算法破解網(wǎng)易易盾拼圖驗(yàn)證
相關(guān)文章
Python數(shù)學(xué)建模學(xué)習(xí)模擬退火算法多變量函數(shù)優(yōu)化示例解析
模擬退火算法借鑒了統(tǒng)計(jì)物理學(xué)的思想,是一種簡(jiǎn)單、通用的啟發(fā)式優(yōu)化算法,并在理論上具有概率性全局優(yōu)化性能,因而在科研和工程中得到了廣泛的應(yīng)用2021-10-10
用Python 爬取貓眼電影數(shù)據(jù)分析《無(wú)名之輩》
這篇文章主要介紹了用Python 爬取貓眼電影數(shù)據(jù)分析《無(wú)名之輩》,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
如何利用python實(shí)現(xiàn)Simhash算法
這篇文章主要介紹了如何利用python實(shí)現(xiàn)Simhash算法,文章基于python的相關(guān)資料展開(kāi)Simhash算法的詳細(xì)介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06
Python代碼使用 Pyftpdlib實(shí)現(xiàn)FTP服務(wù)器功能
FTP 服務(wù)器,在此之前我都是使用Linux的vsftpd軟件包來(lái)搭建FTP服務(wù)器的,現(xiàn)在發(fā)現(xiàn)了利用pyftpdlib可以更加簡(jiǎn)單的方法即可實(shí)現(xiàn)FTP服務(wù)器的功能 ,需要的朋友可以參考下2019-07-07
pandas分組排序 如何獲取第二大的數(shù)據(jù)
這篇文章主要介紹了pandas分組排序 獲取第二大的數(shù)據(jù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
python 通過(guò)pip freeze、dowload打離線(xiàn)包及自動(dòng)安裝的過(guò)程詳解(適用于保密的離線(xiàn)環(huán)境
這篇文章主要介紹了python 通過(guò)pip freeze、dowload打離線(xiàn)包及自動(dòng)安裝【適用于保密的離線(xiàn)環(huán)境】,本文通圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

