drf實現圖片驗證碼功能


一.背景

  在之前實現過django的圖片驗證碼,有自己實現過的,也有基於django-simple-captcha的,都是基於form表單驗證,若自己實現,可以獲取相應的標簽name便可以獲取判斷,若使用django-simple-captcha只需相應配置即可。但在前后端分離的情況下,就有點摸不着頭腦了,序列化時CaptchaField不管作用,百度搜索也沒找到相應的辦法,於是心想只有重寫restframework_jwt自帶的登錄驗證接口及注冊接口,都得新增字段。

二.django-simple-captcha簡單介紹

  Django Simple Captcha是一個非常簡單但高度可定制的Django應用程序,可以將captcha圖像添加到任何Django表單中。

  網址如下:https://django-simple-captcha.readthedocs.io/en/latest/,只需按照文檔簡單配置即可,該插件會對應生成一張表,存放驗證碼信息及過期時間等。

三.基於django-simple-captcha的drf圖片驗證碼

  1.生成圖片驗證碼接口(我這里將圖片轉換成了base64),也可以是圖片或地址:

from django.http import HttpResponse
from captcha.views import CaptchaStore, captcha_image
import base64
.......
class ImageView(APIView):
    def get(self, request):
        hashkey = CaptchaStore.generate_key()
        try:
        #獲取圖片id
            id_ = CaptchaStore.objects.filter(hashkey=hashkey).first().id
            imgage = captcha_image(request, hashkey)
        #將圖片轉換為base64
            image_base = base64.b64encode(imgage.content)
            json_data = json.dumps({"id": id_, "image_base": image_base.decode('utf-8')})
        except:
            json_data = None
        return HttpResponse(json_data, content_type="application/json")                

 

  2.注冊時的圖片驗證碼:

    2.1序列化:

    這里把圖片驗證碼的id和用戶輸入的內容都傳過來,不易錯誤,captcha生成的表中的字段expiration是生成圖片的時間加五分鍾,因此captcha判斷的也就是這個過期時間,重寫也可以判斷這個時間,若當前時間大於它則過期(有效時間五分鍾)。

class UserRegSerializer(serializers.ModelSerializer):
    """"
    用戶注冊序列化
    """
    username = serializers.CharField(min_length=2, max_length=20,
                                     error_messages={
                                         "max_length": "用戶名長度應小於等於20",
                                         "min_length": "用戶名長度應大於等於2"},
                                     validators=[UniqueValidator(queryset=User.objects.all(), message='用戶名已經被使用')],help_text="用戶名")
    code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4, label='驗證碼',
                                 error_messages={
                                     "blank": "請輸入郵箱驗證碼",
                                     "required": "郵箱驗證碼不能為空",
                                     "max_length": "郵箱驗證碼格式錯誤",
                                     "min_length": "郵箱驗證碼格式錯誤"
                                 }, help_text='郵箱驗證碼')
    email = serializers.CharField(required=True, allow_blank=False,
                                  validators=[UniqueValidator(queryset=User.objects.all(), message='用戶已經存在')],help_text="郵箱")
    password = serializers.CharField(style={"input_type": "password"}, write_only=True,help_text="密碼")
    captcha = serializers.CharField(min_length=4, max_length=4, required=True,
                                    error_messages={
                                        "max_length": "圖片驗證碼格式錯誤",
                                        "min_length": "圖片驗證碼格式錯誤",
                                        "required": "請輸入圖片驗證碼"
                                    },help_text="圖片驗證嗎")
    ima_id = serializers.CharField(required=True, write_only=True, allow_blank=False,help_text="圖片驗證碼id")

    def validate_code(self, code):
        verify_codes = EmailVeriyRecord.objects.filter(email=self.initial_data['email']).order_by('-send_time')
        if verify_codes:
            last_verfycode = verify_codes[0]
            five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            if five_minute_ago > last_verfycode.send_time:
                raise serializers.ValidationError('驗證碼過期')
            if code != last_verfycode.code:
                raise serializers.ValidationError('驗證碼錯誤')
        else:
            raise serializers.ValidationError('驗證碼錯誤')

    def validate_password(self, password):
        """
          密碼長度大於6小於12
        """
        if re.match(REGEX_PWD, password):
            pass
        else:
            raise serializers.ValidationError("密碼必須包含數字,字母,特殊符中兩到三種,且長度在6-12之間")
        return password

    def validate_captcha(self, captcha):
        try:
            captcha = captcha.lower()
        except:
            raise serializers.ValidationError("圖片驗證碼錯誤")
        image_code = CaptchaStore.objects.filter(
            id=self.initial_data['ima_id']).first()
        if image_code and datetime.now() > image_code.expiration:
            raise serializers.ValidationError('圖片驗證碼過期')
        else:
            if image_code and image_code.response == captcha:
                pass
            else:
                raise serializers.ValidationError("圖片驗證碼錯誤")

    # 作用於所有字段
    def validate(self, attrs):
        if attrs['username']:
            pass
        else:
            attrs['username'] = attrs['email']
        del attrs["code"]
        del attrs["ima_id"]
        del attrs["captcha"]
        return attrs

    class Meta:
        model = User
        fields = ('email', 'code', 'password', 'username', 'captcha', 'ima_id')

 

     2.2注冊view實現:

class UserRegisterViewset(mixins.CreateModelMixin, mixins.UpdateModelMixin,
                          mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    """
       用戶注冊接口
    create:
        用戶添加
    """
    serializer_class = UserRegSerializer
    queryset = User.objects.all()
    authentication_classes = (JSONWebTokenAuthentication, authentication.SessionAuthentication)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        re_dict['token'] = jwt_encode_handler(payload)
        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

    def get_serializer_class(self):
        '''
        重載GenericAPIView中的get_serializer_class函數,調用不同的序列化類,如果是create,
        就調用UserRegSerializer序列化,否則UserDetailSerializer序列化
        :return: 
        '''
        if self.action == 'retrieve':
            return UserDetailSerializer
        elif self.action == 'create':
            return UserRegSerializer
        return UserDetailSerializer

    def get_permissions(self):
        '''
        重載APIview中的get_perimissions函數,如果是新增用戶則不用登錄,否則必須登錄
        :return: 
        '''
        if self.action == 'retrieve':
            return [permissions.IsAuthenticated()]
        elif self.action == 'create':
            return []
        return []

    def get_object(self):
        '''
        返回當前用戶
        :return: 
        '''
        return self.request.user

    def perform_create(self, serializer):
        return serializer.save()

 

   2.登錄:

    2.1序列化:

from rest_framework_jwt.settings import api_settings

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from django.contrib.auth import authenticate
from rest_framework_jwt.compat import PasswordField


class MyloginSerializer(JSONWebTokenSerializer):
    """
    從寫登錄序列化
    """

    def __init__(self, *args, **kwargs):
        """
        Dynamically add the USERNAME_FIELD to self.fields.
        """
        super(JSONWebTokenSerializer, self).__init__(*args, **kwargs)

        self.fields[self.username_field] = serializers.CharField()
        self.fields['password'] = PasswordField(write_only=True)
        self.fields['captcha'] = serializers.CharField(min_length=4, max_length=4, required=True,
                                                       error_messages={
                                                           "max_length": "圖片驗證碼格式錯誤",
                                                           "min_length": "圖片驗證碼格式錯誤",
                                                           "required": "請輸入圖片驗證碼"
                                                       })
        self.fields['ima_id'] = serializers.CharField(required=True, allow_blank=False)

    def validate_captcha(self, captcha):
        image_code = CaptchaStore.objects.filter(
            id=self.initial_data['ima_id']).first()
        five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
        if image_code and five_minute_ago > image_code.expiration:
            raise serializers.ValidationError('驗證碼過期')
        else:
            if image_code and (image_code.response == captcha or image_code.challenge == captcha):
                pass
            else:
                raise serializers.ValidationError("圖片驗證碼錯誤")

    def validate(self, attrs):
        del attrs["ima_id"]
        del attrs["captcha"]
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            user = authenticate(**credentials)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                payload = jwt_payload_handler(user)

                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
            else:
                msg = _('Unable to log in with provided credentials.')
                raise serializers.ValidationError(msg)
        else:
            msg = _('Must include "{username_field}" and "password".')
            msg = msg.format(username_field=self.username_field)
            raise serializers.ValidationError(msg)

    2.2view:

from rest_framework_jwt.views import JSONWebTokenAPIView
from .serializers import MyloginSerializer

class MyJSONWebToken(JSONWebTokenAPIView):
    """"
    重寫jwt的登錄驗證,含圖片驗證碼
    """
    serializer_class = MyloginSerializer

  3.url中相應配置:

    url(r'images/$',ImageView.as_view()),
    url(r'login/$',MyJSONWebToken.as_view(),name="login")

 

四.總結

  這樣做功能是實現了,但感覺有些粗糙,可以把驗證碼表的數據超過某個時間的自動清除,也可以把數據放入緩存(redis等),希望能提一些改進的建議。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM