ppzhang

图片验证码逻辑

  1. 客户端发起GET连接请求,并随机生成UUID,绑定图片
    • UUID:通用唯一识别码(Universally Unique Identifier),目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,每个人都可以创建不与其它人冲突的UUID
  2. 服务端生成图片验证码,图片存入内存并返回到客户端
  3. 服务端存储源字符串到session中,也可以存入缓存中,例memcachedredis
  4. 客户端表单填写验证码原值
  5. 移出表单框时间触发异步post请求验证,访问时,图片uuid作为属性绑定到表单属性中,作为post提交的数据一部分
  6. 服务端验证时通过UUIDkey,表单值为value进行图片验证码校验

图片验证码使用

  下载pillow

pip install pillow

  在使用的时候需要设置pillow需要的字体。需要复制到django项目中

 

 

设置字体文件的路径

FONTS_DIRS =  os.path.join(BASE_DIR, \'fonts\',)   #找到字体文件的路径
setting.py

生成图片

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),
    

]
urls.py
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\')
views.py

 

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;
    },
regist.vue

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>
View Code

 

带图片验证码的用户注册

<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>
regist.vue

 

"""
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登录返回函数
}
setting.py
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()),

]
user/urls.py
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
user/serializers.py
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)
user/model.py
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)
user/views.py

 

分类:

技术点:

相关文章: