图片验证码逻辑
- 客户端发起GET连接请求,并随机生成UUID,绑定图片
UUID
:通用唯一识别码(Universally Unique Identifier
),目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,每个人都可以创建不与其它人冲突的UUID
- 服务端生成图片验证码,图片存入内存并返回到客户端
- 服务端存储源字符串到
session
中,也可以存入缓存中,例memcached
、redis
- 客户端表单填写验证码原值
- 移出表单框时间触发异步
post
请求验证,访问时,图片uuid
作为属性绑定到表单属性中,作为post
提交的数据一部分 - 服务端验证时通过
UUID
为key
,表单值为value进行图片验证码校验
图片验证码使用
下载pillow
pip install pillow
在使用的时候需要设置pillow需要的字体。需要复制到django项目中
设置字体文件的路径
FONTS_DIRS = os.path.join(BASE_DIR, 'fonts',) #找到字体文件的路径
生成图片
from django.contrib import admin from django.urls import path from . import views urlpatterns = [ path("generate_image_code/<str:generate_image_id>/",views.generate_image_code), ]
from PIL import Image, ImageDraw, ImageFont from shiyanloupro.settings import * from django.http.response import HttpResponse def generate_image_code(request, generate_image_id): ''' 本地图片验证码生成函数 ''' bgcolor = (random.randrange(20, 100), random.randrange( 20, 100), random.randrange(20, 100)) width = 110 height = 40 # 创建画面对象 im = Image.new('RGB', (width, height), bgcolor) # 创建画笔对象 draw = ImageDraw.Draw(im) # 调用画笔的point()函数绘制噪点 for i in range(0, 100): xy = (random.randrange(0, width), random.randrange(0, height)) fill = (random.randrange(0, 255), 255, random.randrange(0, 255)) draw.point(xy, fill=fill) # 定义验证码的备选值 str = '1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm' # 随机选取4个值作为验证码 rand_str = '' for i in range(0, 4): rand_str += str[random.randrange(0, len(str))] # 构造字体对象 fonts_files = os.path.join( FONTS_DIRS, 'SourceCodePro-Bold.ttf') font = ImageFont.truetype(fonts_files, 30) # 构造字体颜色 fontcolor1 = (255, random.randrange(0, 255), random.randrange(0, 255)) fontcolor2 = (255, random.randrange(0, 255), random.randrange(0, 255)) fontcolor3 = (255, random.randrange(0, 255), random.randrange(0, 255)) fontcolor4 = (255, random.randrange(0, 255), random.randrange(0, 255)) # 绘制4个字 draw.text((5, 2), rand_str[0], font=font, fill=fontcolor1) draw.text((25, 2), rand_str[1], font=font, fill=fontcolor2) draw.text((50, 2), rand_str[2], font=font, fill=fontcolor3) draw.text((75, 2), rand_str[3], font=font, fill=fontcolor4) # 释放画笔 del draw # 存入缓存,用于做进一步验证,并设置超时时间为10分组 cache.set(generate_image_id,rand_str,60*10) buf = io.BytesIO() # 将图片保存在内存中,文件类型为png im.save(buf, 'png') # 将内存中的图片数据返回给客户端,MIME类型为图片png! return HttpResponse(buf.getvalue(), 'image/png')
vue生成uuid
generate_uuid: function() { var d = new Date().getTime(); if (window.performance && typeof window.performance.now === "function") { d += performance.now(); //use high-precision timer if available } var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function(c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == "x" ? r : (r & 0x3) | 0x8).toString(16); } ); return uuid; },
vue请求图片验证码
<template> <div> <p><img @click="refresh()" :src="'http://127.0.0.1:8000/user/generate_image_code/' + uuid" /></p> <p>验证码<input type="text" v-model="code"></p> </div> </template> <script> import axios from 'axios' export default { name:"regist", data() { return { code:'', uuid:'', } }, methods: { generate_uuid: function() { var d = new Date().getTime(); if (window.performance && typeof window.performance.now === "function") { d += performance.now(); //use high-precision timer if available } var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function(c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == "x" ? r : (r & 0x3) | 0x8).toString(16); } ); return uuid; }, refresh(){ this.uuid = this.generate_uuid() } }, mounted() { this.uuid = this.generate_uuid() }, } </script>
带图片验证码的用户注册
<template> <div> <p>用户名:<input type="text" v-model="name"></p> <p>密码:<input type="password" v-model="pwd"></p> <p>手机号:<input type="text" v-model="phone"></p> <p>邮箱:<input type="email" v-model="email"></p> <p><img @click="refresh()" :src="'http://127.0.0.1:8000/user/generate_image_code/' + uuid" /></p> <p>验证码<input type="text" v-model="code" @blur="check"></p> <p><button @click="regist_user()">注册</button></p> </div> </template> <script> import axios from 'axios' export default { name:"regist", data() { return { code:'', name:'', pwd:'', phone:'', email:'', uuid:'', is_ok:false } }, methods: { generate_uuid: function() { var d = new Date().getTime(); if (window.performance && typeof window.performance.now === "function") { d += performance.now(); //use high-precision timer if available } var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function(c) { var r = (d + Math.random() * 16) % 16 | 0; d = Math.floor(d / 16); return (c == "x" ? r : (r & 0x3) | 0x8).toString(16); } ); return uuid; }, refresh(){ this.uuid = this.generate_uuid() }, check(){ let post_data = new FormData() post_data.append('generate_image_id',this.uuid) post_data.append('user_code',this.code) axios({ url: 'http://127.0.0.1:8000/user/check/', method: 'post', data: post_data, }).then(res=>{ console.log(res.data) if (res.data.code==200){ this.is_ok=true }else{ this.is_ok=false } }) }, regist_user(){ if(this.is_ok==true){ var form_data = new FormData() form_data.append("username",this.name) form_data.append("password",this.pwd) form_data.append("phone",this.phone) form_data.append("email",this.email) axios({ url: 'http://127.0.0.1:8000/user/users/', method: 'post', data: form_data, }).then(res=>{ console.log(res.data) sessionStorage.setItem("jwt_token",res.data.token) alert("注册成功") }) }else{ alert("注册前请输入正确的用户码") } } }, mounted() { this.uuid = this.generate_uuid() }, } </script>
在Django项目中添加
""" Django settings for shiyanloupro project. Generated by 'django-admin startproject' using Django 2.2.7. For more information on this file, see https://docs.djangoproject.com/en/2.2/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'f84z(fu2k-n^*38fn+o5xbx0wyxq*hrk-rs7__75p0ux$x8s2*' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'corsheaders', 'rest_framework.authtoken', 'userapp' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', # 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'shiyanloupro.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')] , 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'shiyanloupro.wsgi.application' # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', # 数据库主机 'PORT': 3306, # 数据库端口 'USER': 'root', # 数据库用户名 'PASSWORD': 'root', # 数据库用户密码 'NAME': 'shiyanlou' # 数据库名字 } } # Password validation # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ STATIC_URL = '/static/' CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_WHITELIST = () CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', ) FONTS_DIRS = os.path.join(BASE_DIR, 'fonts',) CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", }, } } AUTH_USER_MODEL='userapp.User' REST_FRAMEWORK = { # 身份认证 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', ), #全局配置接口权限 # 'DEFAULT_PERMISSION_CLASSES': ( # 'rest_framework.permissions.IsAuthenticated', # ), } import datetime JWT_AUTH = { 'JWT_AUTH_HEADER_PREFIX': 'JWT', 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), 'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.views.jwt_response_payload_handler', # 重新login登录返回函数 }
from django.contrib import admin from django.urls import path from . import views urlpatterns = [ path("generate_image_code/<str:generate_image_id>/",views.generate_image_code), path("check/",views.CheckCode.as_view()), path("users/",views.UserView.as_view()), ]
from rest_framework_jwt.settings import api_settings from rest_framework import serializers from userapp.models import User class UserSerializer(serializers.Serializer): id =serializers.IntegerField(read_only=True) username = serializers.CharField() password = serializers.CharField() phone = serializers.CharField() email = serializers.CharField() token = serializers.CharField(read_only=True) def create(self, data): user = User.objects.create(**data) #数据库里密码的加密(固定的步骤) user.set_password(data.get('password')) user.save() # 补充生成记录登录状态的token 固定的格式,用过来生成jwt的token jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) token = jwt_encode_handler(payload) #把token发放在user里返回 user.token = token return user
1 from django.db import models 2 from django.contrib.auth.models import AbstractUser 3 # Create your models here. 4 5 class User(AbstractUser): 6 username = models.CharField(max_length=64, unique=True) 7 password = models.CharField(max_length=255) 8 phone = models.CharField(max_length=64) 9 email = models.CharField(max_length=64)
1 from django.http.response import HttpResponse 2 from PIL import Image, ImageDraw, ImageFont 3 from shiyanloupro.settings import * 4 from django.core.cache import cache 5 from rest_framework.views import APIView 6 from rest_framework.response import Response 7 from .serializers import UserSerializer 8 import random,os,io 9 10 11 def generate_image_code(request, generate_image_id): 12 ''' 13 本地图片验证码生成函数 14 ''' 15 bgcolor = (random.randrange(20, 100), random.randrange( 16 20, 100), random.randrange(20, 100)) 17 width = 110 18 height = 40 19 # 创建画面对象 20 im = Image.new('RGB', (width, height), bgcolor) 21 # 创建画笔对象 22 draw = ImageDraw.Draw(im) 23 # 调用画笔的point()函数绘制噪点 24 for i in range(0, 100): 25 xy = (random.randrange(0, width), random.randrange(0, height)) 26 fill = (random.randrange(0, 255), 255, random.randrange(0, 255)) 27 draw.point(xy, fill=fill) 28 # 定义验证码的备选值 29 str = '1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm' 30 # 随机选取4个值作为验证码 31 rand_str = '' 32 for i in range(0, 4): 33 rand_str += str[random.randrange(0, len(str))] 34 # 构造字体对象 35 36 fonts_files = os.path.join( 37 FONTS_DIRS, 'SourceCodePro-Bold.ttf') 38 font = ImageFont.truetype(fonts_files, 30) 39 # 构造字体颜色 40 fontcolor1 = (255, random.randrange(0, 255), random.randrange(0, 255)) 41 fontcolor2 = (255, random.randrange(0, 255), random.randrange(0, 255)) 42 fontcolor3 = (255, random.randrange(0, 255), random.randrange(0, 255)) 43 fontcolor4 = (255, random.randrange(0, 255), random.randrange(0, 255)) 44 # 绘制4个字 45 draw.text((5, 2), rand_str[0], font=font, fill=fontcolor1) 46 draw.text((25, 2), rand_str[1], font=font, fill=fontcolor2) 47 draw.text((50, 2), rand_str[2], font=font, fill=fontcolor3) 48 draw.text((75, 2), rand_str[3], font=font, fill=fontcolor4) 49 # 释放画笔 50 del draw 51 # 存入缓存,用于做进一步验证,并设置超时时间为10分组 52 cache.set(generate_image_id,rand_str,60*10) 53 buf = io.BytesIO() 54 # 将图片保存在内存中,文件类型为png 55 im.save(buf, 'png') 56 # 将内存中的图片数据返回给客户端,MIME类型为图片png! 57 return HttpResponse(buf.getvalue(), 'image/png') 58 59 60 61 class CheckCode(APIView): 62 def post(self,request): 63 generate_image_id = request.data.get('generate_image_id',"") 64 data_code = cache.get(generate_image_id) 65 user_code = request.data.get('user_code',"") 66 if data_code and user_code: 67 print(data_code,user_code) 68 if data_code.lower() == user_code.lower(): 69 return Response({'code':200}) 70 return Response({'code':201}) 71 72 73 74 class UserView(APIView): 75 def post(self,request): 76 serializer = UserSerializer(data=request.data) 77 if serializer.is_valid(): 78 serializer.save() 79 return Response(serializer.data, status=200) 80 return Response(serializer.errors, status=200)