Django实现图片验证码
1.验证码的作用
- 防恶意破解密码:防止,使用程序或机器人恶意去试密码.为了提高用户的体验,用户输入错误以后,才会要求输入验证码.
- 有效防止注册,以防,使用程序或机器人去无限制注册账号.
- 防止一些投票网站的恶意刷票
验证码原理
2.任务逻辑
前端功能
- 点击按钮Ajax调用发送验证码功能
- 输完验证码后Ajax调用验证功能
后端功能
- 发送验证码功能
- 生成指定长度验证码
- 使用Redis代替session缓存, 存储数据! 设置过期时间(1.60秒内无法重新发送短信,2. 设置短信验证码的有效时间)
- 验证码检查
以下是用django2.2版本实现的,默认你已经创建好项目,验证码为下图所示样子:
-
首先在项目的应用下插入一个生成验证码的文件captcha.py。这是生成验证码的一个文件已经封装好啦,直接调用就可以了。代码如下:
“”"
图片验证码
“”"
import os
import randomfrom io import BytesIO from PIL import Image from PIL import ImageFilter from PIL.ImageDraw import Draw from PIL.ImageFont import truetype class Bezier: """贝塞尔曲线""" def __init__(self): self.tsequence = tuple([t / 20.0 for t in range(21)]) self.beziers = {} def make_bezier(self, n): """绘制贝塞尔曲线""" try: return self.beziers[n] except KeyError: combinations = pascal_row(n - 1) result = [] for t in self.tsequence: tpowers = (t ** i for i in range(n)) upowers = ((1 - t) ** i for i in range(n - 1, -1, -1)) coefs = [c * a * b for c, a, b in zip(combinations, tpowers, upowers)] result.append(coefs) self.beziers[n] = result return result class Captcha: """验证码""" def __init__(self, width, height, fonts=None, color=None): self._image = None self._fonts = fonts if fonts else \ [os.path.join(os.path.dirname(__file__), 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'Action.ttf']] self._color = color if color else random_color(0, 200, random.randint(220, 255)) self._width, self._height = width, height @classmethod def instance(cls, width=200, height=75): prop_name = f'_instance_{width}_{height}' if not hasattr(cls, prop_name): setattr(cls, prop_name, cls(width, height)) return getattr(cls, prop_name) def _background(self): """绘制背景""" Draw(self._image).rectangle([(0, 0), self._image.size], fill=random_color(230, 255)) def _smooth(self): """平滑图像""" return self._image.filter(ImageFilter.SMOOTH) def _curve(self, width=4, number=6, color=None): """绘制曲线""" dx, height = self._image.size dx /= number path = [(dx * i, random.randint(0, height)) for i in range(1, number)] bcoefs = Bezier().make_bezier(number - 1) points = [] for coefs in bcoefs: points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)]) for ps in zip(*path))) Draw(self._image).line(points, fill=color if color else self._color, width=width) def _noise(self, number=50, level=2, color=None): """绘制扰码""" width, height = self._image.size dx, dy = width / 10, height / 10 width, height = width - dx, height - dy draw = Draw(self._image) for i in range(number): x = int(random.uniform(dx, width)) y = int(random.uniform(dy, height)) draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level) def _text(self, captcha_text, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None): """绘制文本""" color = color if color else self._color fonts = tuple([truetype(name, size) for name in fonts for size in font_sizes or (65, 70, 75)]) draw = Draw(self._image) char_images = [] for c in captcha_text: font = random.choice(fonts) c_width, c_height = draw.textsize(c, font=font) char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0)) char_draw = Draw(char_image) char_draw.text((0, 0), c, font=font, fill=color) char_image = char_image.crop(char_image.getbbox()) for drawing in drawings: d = getattr(self, drawing) char_image = d(char_image) char_images.append(char_image) width, height = self._image.size offset = int((width - sum(int(i.size[0] * squeeze_factor) for i in char_images[:-1]) - char_images[-1].size[0]) / 2) for char_image in char_images: c_width, c_height = char_image.size mask = char_image.convert('L').point(lambda i: i * 1.97) self._image.paste(char_image, (offset, int((height - c_height) / 2)), mask) offset += int(c_width * squeeze_factor) @staticmethod def _warp(image, dx_factor=0.3, dy_factor=0.3): """图像扭曲""" width, height = image.size dx = width * dx_factor dy = height * dy_factor x1 = int(random.uniform(-dx, dx)) y1 = int(random.uniform(-dy, dy)) x2 = int(random.uniform(-dx, dx)) y2 = int(random.uniform(-dy, dy)) warp_image = Image.new( 'RGB', (width + abs(x1) + abs(x2), height + abs(y1) + abs(y2))) warp_image.paste(image, (abs(x1), abs(y1))) width2, height2 = warp_image.size return warp_image.transform( (width, height), Image.QUAD, (x1, y1, -x1, height2 - y2, width2 + x2, height2 + y2, width2 - x2, -y1)) @staticmethod def _offset(image, dx_factor=0.1, dy_factor=0.2): """图像偏移""" width, height = image.size dx = int(random.random() * width * dx_factor) dy = int(random.random() * height * dy_factor) offset_image = Image.new('RGB', (width + dx, height + dy)) offset_image.paste(image, (dx, dy)) return offset_image @staticmethod def _rotate(image, angle=25): """图像旋转""" return image.rotate(random.uniform(-angle, angle), Image.BILINEAR, expand=1) def generate(self, captcha_text='', fmt='PNG'): """生成验证码(文字和图片)""" self._image = Image.new('RGB', (self._width, self._height), (255, 255, 255)) self._background() self._text(captcha_text, self._fonts, drawings=['_warp', '_rotate', '_offset']) self._curve() self._noise() self._smooth() image_bytes = BytesIO() self._image.save(image_bytes, format=fmt) return image_bytes.getvalue() def pascal_row(n=0): """生成毕达哥拉斯三角形(杨辉三角)""" result = [1] x, numerator = 1, n for denominator in range(1, n // 2 + 1): x *= numerator x /= denominator result.append(x) numerator -= 1 if n & 1 == 0: result.extend(reversed(result[:-1])) else: result.extend(reversed(result)) return result def random_color(start=0, end=255, opacity=255): """获得随机颜色""" red = random.randint(start, end) green = random.randint(start, end) blue = random.randint(start, end) if opacity is None: return red, green, blue return red, green, blue, opacity
并导入支持的字体文件:
-
在应用下新建一个util.py的文件用来保存一些常用的工具函数,
def gen_verify_code(length = 4):
‘’‘生成指定长度验证码’’’all_chars = '0123456789abcdefghijklmnopqrstuvwxyz' return ''.join(random.choices(all_chars,k = length))
-
在试图函数view.py中定义一个函数生成验证码:
def captcha(request):
‘’‘生成验证码’’’
code_text = gen_verify_code() # 随机生成4位数字
request.session[‘code’] = code_text # 在服务器中以session形式保存
image_data = Captcha.instance().generate(code_text)
return HttpResponse(image_data, content_type=‘image/png’) # 返回图片对象 -
前端代码为
#验证码的试图函数
5.设置点击随机切换的效果(使用jQuery方法,添加点击事件)
<script>
$(()=>{
$('#code').on('click',(evt) =>{
$(evt.target).attr('src','/polls/captcha/?' + Math.random())
}
)
})
</script>
6.登陆时进行验证的代码为:
# 获取客户端输入的验证码
code_from_user = request.POST.get('captcha', '0')
# 获取服务器session存储的验证码
code_from_serv = request.session.get('code', '1')
# 忽略大小写进行判断是否一致
if code_from_serv.lower() == code_from_user.lower():
username = request.POST.get('username')
password = request.POST.get('password')