Skip to content

Commit 23ae9a8

Browse files
committed
feat:login captcha
1 parent 1b3535b commit 23ae9a8

4 files changed

Lines changed: 91 additions & 4 deletions

File tree

app/api/cms/user.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
:copyright: © 2020 by the Lin team.
55
:license: MIT, see LICENSE for more details.
66
"""
7-
from operator import and_
8-
7+
from flask import current_app, request
98
from flask_jwt_extended import (
109
create_access_token,
1110
create_refresh_token,
1211
get_current_user,
1312
get_jwt_identity,
1413
verify_jwt_refresh_token_in_request,
1514
)
15+
from itsdangerous import JSONWebSignatureSerializer as JWSSerializer
1616
from lin import manager, permission_meta
1717
from lin.db import db
1818
from lin.exception import Duplicated, Failed, NotFound, ParameterError, Success
@@ -21,6 +21,7 @@
2121
from lin.redprint import Redprint
2222

2323
from app.exception.api import RefreshFailed
24+
from app.util.captcha import CaptchaTool
2425
from app.util.common import split_group
2526
from app.validator.form import (
2627
ChangePasswordForm,
@@ -48,13 +49,20 @@ def register():
4849

4950

5051
@user_api.route("/login", methods=["POST"])
51-
@permission_meta(name="登录", module="用户", mount=False)
5252
def login():
5353
form = LoginForm().validate_for_api()
54+
# 校对验证码
55+
if current_app.config.get("LOGIN_CAPTCHA"):
56+
tag = request.headers.get("tag")
57+
secret_key = current_app.config.get("SECRET_KEY")
58+
serializer = JWSSerializer(secret_key)
59+
if form.captcha.data != serializer.loads(tag):
60+
raise Failed("验证码校验失败")
61+
5462
user = manager.user_model.verify(form.username.data, form.password.data)
5563
# 用户未登录,此处不能用装饰器记录日志
5664
Log.create_log(
57-
message=f"{user.username}登陆成功获取了令牌",
65+
message=f"{user.username}登录成功获取了令牌",
5866
user_id=user.id,
5967
username=user.username,
6068
status_code=200,
@@ -169,3 +177,17 @@ def _register_user(form: RegisterForm):
169177
user_group.user_id = user.id
170178
user_group.group_id = group_id
171179
db.session.add(user_group)
180+
181+
182+
@user_api.route("/captcha", methods=["GET", "POST"])
183+
def get_captcha():
184+
"""
185+
获取图形验证码
186+
"""
187+
if not current_app.config.get("LOGIN_CAPTCHA"):
188+
return {"tag": "", "image": ""}
189+
image, code = CaptchaTool().get_verify_code()
190+
secret_key = current_app.config.get("SECRET_KEY")
191+
serializer = JWSSerializer(secret_key)
192+
tag = serializer.dumps(code)
193+
return {"tag": tag, "image": image}

app/config/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class BaseConfig(object):
3232
# 令牌配置
3333
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
3434

35+
# 启用验证码登录
36+
LOGIN_CAPTCHA = True
3537
# 默认文件上传配置
3638
FILE = {
3739
"STORE_DIR": "assets",

app/util/captcha.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import base64
2+
import io
3+
import random
4+
import string
5+
6+
from PIL import Image, ImageDraw, ImageFont
7+
8+
9+
class CaptchaTool:
10+
"""
11+
生成图片验证码
12+
"""
13+
14+
def __init__(self, width=50, height=12):
15+
16+
self.width = width
17+
self.height = height
18+
# 新图片对象
19+
self.im = Image.new("RGB", (width, height), "white")
20+
# 字体
21+
self.font = ImageFont.load_default()
22+
# draw对象
23+
self.draw = ImageDraw.Draw(self.im)
24+
25+
def draw_lines(self, num=3):
26+
"""
27+
划线
28+
"""
29+
for num in range(num):
30+
x1 = random.randint(0, self.width / 2)
31+
y1 = random.randint(0, self.height / 2)
32+
x2 = random.randint(0, self.width)
33+
y2 = random.randint(self.height / 2, self.height)
34+
self.draw.line(((x1, y1), (x2, y2)), fill="black", width=1)
35+
36+
def get_verify_code(self):
37+
"""
38+
生成验证码图形
39+
"""
40+
# 设置随机4位数字验证码
41+
code = "".join(random.sample(string.digits, 4))
42+
# 绘制字符串
43+
for item in range(4):
44+
self.draw.text(
45+
(6 + random.randint(-3, 3) + 10 * item, 2 + random.randint(-2, 2)),
46+
text=code[item],
47+
fill=(
48+
random.randint(32, 127),
49+
random.randint(32, 127),
50+
random.randint(32, 127),
51+
),
52+
font=self.font,
53+
)
54+
# 划线
55+
# self.draw_lines()
56+
# 重新设置图片大小
57+
self.im = self.im.resize((100, 24))
58+
# 图片转为base64字符串
59+
buffered = io.BytesIO()
60+
self.im.save(buffered, format="JPEG")
61+
img_str = b"data:image/png;base64," + base64.b64encode(buffered.getvalue())
62+
return img_str, code

app/validator/form.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def validate_group_ids(self, value):
6666
class LoginForm(Form):
6767
username = StringField(validators=[DataRequired()])
6868
password = PasswordField("密码", validators=[DataRequired(message="密码不可为空")])
69+
captcha = StringField()
6970

7071

7172
# 重置密码校验

0 commit comments

Comments
 (0)