<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          用 Django REST framework 來實現(xiàn)一次性驗證碼(OTP)

          共 14390字,需瀏覽 29分鐘

           ·

          2021-09-03 23:09

          一次性驗證碼,英文是 One Time Password,簡寫為 OTP,又稱動態(tài)密碼或單次有效密碼,是指計算機系統(tǒng)或其他數(shù)字設備上只能使用一次的密碼,有效期為只有一次登錄會話或很短如 1 分鐘。OTP 避免了一些靜態(tài)密碼認證相關(guān)系的缺點,不容易受到重放攻擊,比如常見的注冊場景,用戶的郵箱或短信會收到一條一次性的激活鏈接,或者收到一次隨機的驗證碼(只能使用一次),從而驗證了郵箱或手機號的有效性。

          今天講一下如何用 Django REST framework[1](DRF) 來實現(xiàn) OTP,閱讀本文需要一定的 DRF 的基礎(chǔ)知識。

          要實現(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ā)送驗證碼的時間和對應的郵箱記錄下來,那么就需要設計一張表來存儲。

          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='[email protected]',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ù)保存及序列化,只不過這樣會導致 View 類特別臃腫,不夠優(yōu)雅,不易維護。

          如果有幫助,歡迎關(guān)注、點贊支持。

          留言討論。

          參考資料

          [1]

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


          瀏覽 77
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91视频91最新 | 99精品免费 | 日韩AV网站在线观看 | 久久精品xxx | 免费看黄 片,在线观看 |