一.背景
在之前實現過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等),希望能提一些改進的建議。