• 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等),希望能提一些改进的建议。

     

  • 相关阅读:
    ArrayList去除集合中自定义对象元素的重复值_对象的成员变量值相同
    去除ArrayList集合中重复字符串元素方式_思路:创建新集合方式
    21班_编程入门测试题
    jh_01_编程入门小案例练习
    排列算法
    HDOJ2030汉字统计
    HDOJ2029Palindromes _easy version
    HDOJ2028Lowest Common Multiple Plus
    HDOJ2027统计元音
    HDOJ2026首字母变大写
  • 原文地址:https://www.cnblogs.com/lyq-biu/p/10077820.html
Copyright © 2020-2023  润新知