Django实现验证码
背景知识
1. 验证码的作用
防恶意破解密码:防止,使用程序或机器人恶意去试密码.为了提高用户的体验,用户输入错误以后,才会要求输入验证码.
防论坛灌水:这个是很常见的。有一种程序叫做顶帖机,如果无限制的刷,整个论坛可能到处是拉圾信息,比如,百度贴吧 ,你只要是新用户或者刚刚关注的贴吧,要是发帖,会马上出现验证码。
有效防止注册,以防,使用程序或机器人去无限制注册账号.
防刷票,网上有很多投票类的网站.
2. 验证码的原理
验证码于服务器端生成,发送给客户端,并以图像格式显示。客户端提交所显示的验证码,客户端接收并进行比较,若比对失败则不能实现登录或注册,反之成功后跳转相应界面。
验证码原理与流程
代码实现
废话不多说,先上代码:
# encoding:utf-8
from PIL import Image, ImageDraw, ImageFont
import random, StringIO
import os
from math import ceil
import base64
current_path = os.path.normpath(os.path.dirname(__file__))class Captcha(object):# 定义一个验证码类,def __init__(self, request): self.django_request = request self.session_key = request.session.session_key self.words = [] # image size (pix) self.img_width = 150 self.img_height = 30 # default type self.type = 'number'def _get_font_size(self): """ 将图片高度的80%作为字体大小 """ s1 = int(self.img_height * 0.8) s2 = int(self.img_width / len(self.code)) return int(min((s1, s2)) + max((s1, s2)) * 0.05)def _get_words(self): """ The words list """ # 扩充单词列表 if self.words: return set(self.words) file_path = os.path.join(current_path, 'words.list') f = open(file_path, 'r') return set([line.replace('\n', '') for line in f.readlines()])def _set_answer(self, answer): """ 设置答案 """ self.django_request.session[self.session_key] = str(answer)def _yield_code(self): """ 生成验证码数字,以及答案 """ # 数字公式验证码 def number(): m, n = 1, 50 x = random.randrange(m, n) y = random.randrange(m, n) r = random.randrange(0, 2) if r == 0: code = "%s - %s = ?" % (x, y) z = x - y else: code = "%s + %s = ?" % (x, y) z = x + y self._set_answer(z) return code fun = eval(self.type.lower()) return fun()def display(self): """ 把生成的验证码图片改成数据流返回 """ # 字体颜色 self.font_color = ['black', 'darkblue', 'darkred'] # 背景颜色,随机生成 self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255)) # 字体 self.font_path = os.path.join(current_path, 'timesbi.ttf') # self.font_path = os.path.join(current_path,'Menlo.ttc') # 生成的验证码只做一次验证,就会清空 self.django_request.session[self.session_key] = '' # 使用 PIL创建画布 im = Image.new('RGB', (self.img_width, self.img_height), self.background) # 生成验证码 self.code = self._yield_code() # 设置字体大小 self.font_size = self._get_font_size() # 实例化一个绘图 draw = ImageDraw.Draw(im) # 在画布绘图,写验证码 if self.type == 'word': c = int(8 / len(self.code) * 3) or 3 elif self.type == 'number': c = 4 for i in range(random.randrange(c - 2, c)): line_color = (random.randrange(0, 255), random.randrange(0, 255), random.randrange(0, 255)) xy = ( random.randrange(0, int(self.img_width * 0.2)), random.randrange(0, self.img_height), random.randrange(3 * self.img_width / 4, self.img_width), random.randrange(0, self.img_height) ) draw.line(xy, fill=line_color, width=int(self.font_size * 0.1)) # draw.arc(xy,fill=line_color,width=int(self.font_size*0.1)) # draw.arc(xy,0,1400,fill=line_color) # code part j = int(self.font_size * 0.3) k = int(self.font_size * 0.5) x = random.randrange(j, k) # starts point for i in self.code: # 上下抖动量,字数越多,上下抖动越大 m = int(len(self.code)) y = random.randrange(1, 3) if i in ('+', '=', '?'): # 对计算符号等特殊字符放大处理 m = ceil(self.font_size * 0.8) else: # 字体大小变化量,字数越少,字体大小变化越多 m = random.randrange(0, int(45 / self.font_size) + int(self.font_size / 5)) self.font = ImageFont.truetype(self.font_path.replace('\\', '/'), self.font_size + int(ceil(m))) draw.text((x, y), i, font=self.font, fill=random.choice(self.font_color)) x += self.font_size * 0.9 del x del draw # 序列化处理 buf = StringIO.StringIO() im.save(buf, 'gif') buf.closed data = base64.encodestring(buf.getvalue()) return datadef validate(self, code): """ 检查用户输入和服务器上的密码是否一致 """ if not code: return False _code = self.django_request.session.get(self.session_key) or '' self.django_request.session[self.session_key] = '' return _code.lower() == str(code).lower()def check(self, code): """ 检查用户输入和服务器上保存的密码是否一致 """ return self.validate(code)
上面使用的库如下:
from PIL import Image, ImageDraw, ImageFont import random, StringIO import os from math import ceil import base64
说明:
PIL 画图,生成图片
random 随机生成数 math用于计算
StringIO将图片格式转成数据流用于网络传输
base64,用户编码,数据传输,应前端要求处理跨域API的问题
需要强调的是:
我把用户的验证码的答案保存在用户的session中,保存在服务器上
self.django_request.session[self.session_key] = str(answer)
每一个用户访问都是会实例化一个request.seesion对象,所以,用户区分开了.
self.session_key = request.session.session_key
同一用户在不同地方同时登录,对应的request.session.session_key不同,所以也区分了异地同时登录,出现混乱的情况.
django的view视图
from common.CaptchaVerify import Captchadef captchaCode(request): ca = Captcha(request) ca.type = 'number' raw = ca.display() response = JsonResponse(raw) return responsedef login(request): _code = request.POST.get('code') or '' if not _code: data = {'code': 1, 'messeage': 'verify fail, captchacode error'} response = ReturnJson(data, status=401).get() return response ca = Captcha(request) if not ca.check(_code): data = {'code': 1, 'messsage': 'verify fail, captchacode error'} response = ReturnJson(data, status=401).get() return response
这里,用户可以直接调用,那个类,如果需要定制,可以自己在类中修改,符合自己的业务需求.
ps:
验证码绘制规则
均匀绘画字符,居中
字符颜色要比较深
要有线条雪花等干扰元素
一切能随机的都随机
考虑到用户的体验,老是错误,开始降低难度(哈哈哈!!)
作者:若与
链接:https://www.jianshu.com/p/cfe220d21e9d