From 99fd87dbcf49079a92cd5468ca50d5623919eef9 Mon Sep 17 00:00:00 2001 From: zemal Date: Sun, 20 Aug 2017 20:41:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8D=A2=E5=9B=9E=E4=B9=8B=E5=89=8D=E7=9A=84ca?= =?UTF-8?q?pacha=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- account/views/oj.py | 4 +- utils/captcha/__init__.py | 170 +++++++++++++------------------------- 2 files changed, 61 insertions(+), 113 deletions(-) diff --git a/account/views/oj.py b/account/views/oj.py index 1107a7d4..178337e1 100644 --- a/account/views/oj.py +++ b/account/views/oj.py @@ -237,7 +237,7 @@ class UserRegisterAPI(APIView): """ data = request.data captcha = Captcha(request) - if not captcha.validate(data["captcha"]): + if not captcha.check(data["captcha"]): return self.error("Invalid captcha") if User.objects.filter(username=data["username"]).exists(): return self.error("Username already exists") @@ -260,7 +260,7 @@ class UserChangePasswordAPI(APIView): """ data = request.data captcha = Captcha(request) - if not captcha.validate(data["captcha"]): + if not captcha.check(data["captcha"]): return self.error("Invalid captcha") username = request.user.username user = auth.authenticate(username=username, password=data["old_password"]) diff --git a/utils/captcha/__init__.py b/utils/captcha/__init__.py index ce687d52..b88f4527 100644 --- a/utils/captcha/__init__.py +++ b/utils/captcha/__init__.py @@ -1,5 +1,5 @@ """ -Copyright 2017 TY +Copyright 2013 TY Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12,145 +12,93 @@ limitations under the License. """ import os +import time import random -from math import ceil -from six import BytesIO -from PIL import Image, ImageDraw, ImageFont -__version__ = "0.3.3" -current_path = os.path.normpath(os.path.dirname(__file__)) +from io import BytesIO +from PIL import Image, ImageDraw, ImageFont class Captcha(object): def __init__(self, request): - """ something init """ - + 初始化,设置各种属性 + """ self.django_request = request self.session_key = "_django_captcha_key" - self.words = ["hello", "word"] + self.captcha_expires_time = "_django_captcha_expires_time" - # image size (pix) - self.img_width = 150 + # 验证码图片尺寸 + self.img_width = 90 self.img_height = 30 - self.type = "number" - self.mode = "number" - - def _get_font_size(self): + def _get_font_size(self, code): + """ + 将图片高度的80%作为字体大小 + """ s1 = int(self.img_height * 0.8) - s2 = int(self.img_width / len(self.code)) + s2 = int(self.img_width / len(code)) return int(min((s1, s2)) + max((s1, s2)) * 0.05) - def _get_words(self): - """ words list - """ - - # TODO 扩充单词表 - - if self.words: - return set(self.words) - def _set_answer(self, answer): + """ + 设置答案和过期时间 + """ self.django_request.session[self.session_key] = str(answer) + self.django_request.session[self.captcha_expires_time] = time.time() + 60 - def _generate(self): - # 英文单词验证码 - def word(): - code = random.sample(self._get_words(), 1)[0] - self._set_answer(code) - return code - - # 数字公式验证码 - 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.mode.lower()) - return fun() + def _make_code(self): + """ + 生成随机数或随机字符串 + """ + string = random.sample("abcdefghkmnpqrstuvwxyzABCDEFGHGKMNOPQRSTUVWXYZ23456789", 4) + self._set_answer("".join(string)) + return string def get(self): - """ return captcha image bytes """ + 生成验证码图片,返回值为图片的bytes + """ + background = (random.randrange(200, 255), random.randrange(200, 255), random.randrange(200, 255)) + code_color = (random.randrange(0, 50), random.randrange(0, 50), random.randrange(0, 50), 255) - # font color - self.font_color = ["black", "darkblue", "darkred"] + font_path = os.path.join(os.path.normpath(os.path.dirname(__file__)), "timesbi.ttf") - # background color - self.background = (random.randrange(230, 255), random.randrange(230, 255), random.randrange(230, 255)) + image = Image.new("RGB", (self.img_width, self.img_height), background) + code = self._make_code() + font_size = self._get_font_size(code) + draw = ImageDraw.Draw(image) - # font path - self.font_path = os.path.join(current_path, "timesbi.ttf") # or Menlo.ttc + # x是第一个字母的x坐标 + x = random.randrange(int(font_size * 0.3), int(font_size * 0.5)) - self.django_request.session[self.session_key] = "" - im = Image.new("RGB", (self.img_width, self.img_height), self.background) - self.code = self._generate() + for i in code: + # 字符y坐标 + y = random.randrange(1, 7) + # 随机字符大小 + font = ImageFont.truetype(font_path.replace("\\", "/"), font_size + random.randrange(-3, 7)) + draw.text((x, y), i, font=font, fill=code_color) + # 随机化字符之间的距离 字符粘连可以降低识别率 + x += font_size * random.randrange(6, 8) / 10 - # set font size automaticly - self.font_size = self._get_font_size() - - # creat - draw = ImageDraw.Draw(im) - - # draw noisy point/line - if self.mode == "word": - c = int(8 / len(self.code) * 3) or 3 - elif self.mode == "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(int(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)) - - # main part - j = int(self.font_size * 0.3) - k = int(self.font_size * 0.5) - x = random.randrange(j, k) - - 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 + self.django_request.session[self.session_key] = "".join(code) with BytesIO() as buf: - im.save(buf, "gif") + image.save(buf, "gif") buf_str = buf.getvalue() return buf_str - def validate(self, code): - """ user input validate + def check(self, code): + """ + 检查用户输入的验证码是否正确 """ - - if not code: - return False - - code = code.strip() _code = self.django_request.session.get(self.session_key) or "" - self.django_request.session[self.session_key] = "" - return _code.lower() == str(code).lower() + if not _code: + return False + expires_time = self.django_request.session.get(self.captcha_expires_time) or 0 + # 注意 如果验证之后不清除之前的验证码的话 可能会造成重复验证的现象 + del self.django_request.session[self.session_key] + del self.django_request.session[self.captcha_expires_time] + if _code.lower() == str(code).lower() and time.time() < expires_time: + return True + else: + return False