图片验证码逻辑
- 客户端发起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 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
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class User(AbstractUser): username = models.CharField(max_length=64, unique=True) password = models.CharField(max_length=255) phone = models.CharField(max_length=64) email = models.CharField(max_length=64)
from django.http.response import HttpResponse from PIL import Image, ImageDraw, ImageFont from shiyanloupro.settings import * from django.core.cache import cache from rest_framework.views import APIView from rest_framework.response import Response from .serializers import UserSerializer import random,os,io 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\') class CheckCode(APIView): def post(self,request): generate_image_id = request.data.get(\'generate_image_id\',"") data_code = cache.get(generate_image_id) user_code = request.data.get(\'user_code\',"") if data_code and user_code: print(data_code,user_code) if data_code.lower() == user_code.lower(): return Response({\'code\':200}) return Response({\'code\':201}) class UserView(APIView): def post(self,request): serializer = UserSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=200) return Response(serializer.errors, status=200)