Django實現(xiàn)驗證碼
背景知識
1. 驗證碼的作用
- 防惡意破解密碼:防止,使用程序或機器人惡意去試密碼.為了提高用戶的體驗,用戶輸入錯誤以后,才會要求輸入驗證碼.
- 防論壇灌水:這個是很常見的。有一種程序叫做頂帖機做入,如果無限制的刷,整個論壇可能到處是拉圾信息同衣,比如竟块,百度貼吧 ,你只要是新用戶或者剛剛關注的貼吧耐齐,要是發(fā)帖浪秘,會馬上出現(xiàn)驗證碼。
- 有效防止注冊埠况,以防耸携,使用程序或機器人去無限制注冊賬號.
- 防刷票,網(wǎng)上有很多投票類的網(wǎng)站.
2. 驗證碼的原理
驗證碼于服務器端生成辕翰,發(fā)送給客戶端夺衍,并以圖像格式顯示∠裁客戶端提交所顯示的驗證碼沟沙,客戶端接收并進行比較,若比對失敗則不能實現(xiàn)登錄或注冊壁榕,反之成功后跳轉相應界面尝胆。
驗證碼原理與流程
代碼實現(xiàn)
廢話不多說,先上代碼:
# 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):
""" 生成驗證碼數(shù)字,以及答案
"""
# 數(shù)字公式驗證碼
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):
""" 把生成的驗證碼圖片改成數(shù)據(jù)流返回
"""
# 字體顏色
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創(chuàng)建畫布
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:
# 上下抖動量,字數(shù)越多,上下抖動越大
m = int(len(self.code))
y = random.randrange(1, 3)
if i in ('+', '=', '?'):
# 對計算符號等特殊字符放大處理
m = ceil(self.font_size * 0.8)
else:
# 字體大小變化量,字數(shù)越少,字體大小變化越多
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 data
def 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 隨機生成數(shù) math用于計算
- StringIO將圖片格式轉成數(shù)據(jù)流用于網(wǎng)絡傳輸
- base64,用戶編碼,數(shù)據(jù)傳輸,應前端要求處理跨域API的問題
需要強調的是:
我把用戶的驗證碼的答案保存在用戶的session中,保存在服務器上
self.django_request.session[self.session_key] = str(answer)
每一個用戶訪問都是會實例化一個request.seesion對象,所以,用戶區(qū)分開了.
self.session_key = request.session.session_key
同一用戶在不同地方同時登錄,對應的request.session.session_key不同,所以也區(qū)分了異地同時登錄,出現(xiàn)混亂的情況.
django的view視圖
from common.CaptchaVerify import Captcha
def captchaCode(request):
ca = Captcha(request)
ca.type = 'number'
raw = ca.display()
response = JsonResponse(raw)
return response
def 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
這里,用戶可以直接調用,那個類,如果需要定制,可以自己在類中修改,符合自己的業(yè)務需求.
ps:
驗證碼繪制規(guī)則
- 均勻繪畫字符二庵,居中
- 字符顏色要比較深
- 要有線條雪花等干擾元素
- 一切能隨機的都隨機
- 考慮到用戶的體驗,老是錯誤,開始降低難度(哈哈哈!!)
*下期預告