登录 登录
登录时序图
登录表单渲染
html
<form
class="layui-form login_form"
style="display: none"
lay-filter="login_form"
>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-cellphone"></i>
</div>
<input
type="text"
name="mobile"
value=""
lay-verify="required"
placeholder="电话号码"
lay-reqtext="请填写电话号码"
autocomplete="off"
class="layui-input"
lay-affix="clear"
/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-password"></i>
</div>
<input
type="password"
name="password"
value=""
lay-verify="required"
placeholder="密 码"
lay-reqtext="请填写密码"
autocomplete="off"
class="layui-input"
lay-affix="eye"
/>
</div>
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<div class="layui-input-wrap">
<div class="layui-input-prefix">
<i class="layui-icon layui-icon-vercode"></i>
</div>
<input
type="text"
name="captcha"
value=""
lay-verify="required"
placeholder="验证码"
lay-reqtext="请填写验证码"
autocomplete="off"
class="layui-input"
lay-affix="clear"
/>
</div>
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img id="captchaImage" style="height: 40px" />
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<input
type="checkbox"
name="remember"
lay-skin="primary"
title="记住密码"
/>
<a href="javascript:;" style="float: right; margin-top: 7px;color: #16baaa;"
>重置密码</a
>
<a href="javascript:;" style="float: right; margin-top: 7px;">忘记密码?</a>
</div>
<div class="layui-form-item">
<button
class="layui-btn layui-btn-fluid"
lay-submit
lay-filter="login-submit"
>
登录
</button>
</div>
<div class="layui-form-item demo-login-other">
<label>没有账户?</label>
<a href="javascript:;" style="color: #16baaa;" class="register_btn"
>注册一个</a
>
</div>
</form>点击登录显示表单
javascript
// 点击登录显示表单
$(".login_btn").click(function () {
layer.closeAll("page");
layer.open({
type: 1,
title: '<h2 style="text-align: center">用户登录</h2>',
content: $(".login_form"),
});
});
// 提交事件
form.on("submit(login-submit)", function (data) {
let field = data.field;
layer.alert(JSON.stringify(field), {
title: "当前填写的字段值",
});
// 此处可执行 Ajax 等操作
return false;
});验证码
前端 - 图片验证码
- 客户端请求服务器获得图片验证码
- 服务器返回图片,将生成的验证码保存到数据库
- 客户端携带验证码进行请求,服务器校验数据
- 登录成功之后跳转到首页,登录失败则重新登录
生成 uuid 用于记录验证码
javascript
// 生成 uuid
function generateUUID() {
let d = new Date().getTime();
if (window.performance && typeof window.performance.now === "function") {
d += performance.now(); // use high-precision timer if available
}
let uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
/[xy]/g,
function (c) {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
},
);
return uuid;
}图片验证码更新,先修改验证码显示标签
html
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<!-- [!code --]--> <img src="https://www.oschina.net/action/user/captcha"
onclick="this.src='https://www.oschina.net/action/user/captcha?t='+ new Date().getTime();">
<img id="captchaImage" style="height: 40px">
</div>
</div>编写验证码加载事件
javascript
layui.use(function () {
let form = layui.form;
let layer = layui.layer;
let $ = layui.$;
let captcha_uuid = "";
function gen_captcha() {
captcha_uuid = generateUUID();
// 浏览器要发起图片验证码请求 /captcha_code?captcha_uuid=xxxxx
document.getElementById("captchaImage").src =
"/get_captcha?captcha_uuid=" + captcha_uuid;
}
// 点击按钮更新验证码
$("#captchaImage").click(function () {
gen_captcha();
});
});点击登录的时候,也需要显示验证码
后端 - 生成图片验证码
安装依赖
shell
poetry add captcha然后编写后端的视图
python
# filename: /utils/gen_captcha.py
from io import BytesIO
from random import choices
from captcha.image import ImageCaptcha
from flask import make_response
from PIL import Image
def gen_captcha(content="0123456789"):
"""生成验证码"""
image = ImageCaptcha()
# 获取字符串
captcha_text = "".join(choices(content, k=4))
# 生成图像
captcha_image = Image.open(image.generate(captcha_text))
return captcha_text, captcha_image
# 生成验证码
def get_captcha_code_and_content():
code, image = gen_captcha()
out = BytesIO()
image.save(out, "png")
out.seek(0)
content = out.read() # 读取图片的二进制数据做成响应体
return code, content
if __name__ == '__main__':
code, content = get_captcha_code_and_content()
print(code, content)python
# filename: view/index.py
from forum.utils.gen_captcha import get_captcha_code_and_content
@index_bp.get('/get_captcha')
def get_captcha_view():
# 1. 获取参数
captcha_uuid = request.args.get("captcha_uuid")
# 2. 生成验证码
code, content = get_captcha_code_and_content()
# 3. 记录数据到数据库(用 session 代替)
# session['code'] = code
redis_store.set_chapter_code(captcha_uuid, code)
# 设置响应体位图片
resp = make_response(content) # 读取图片的二进制数据做成响应体
resp.content_type = "image/png"
# 5. 响应返回
return resppython
# filename: form/extensions/init__redis.py
class RedisStore:
# ...
def set_captcha_code(self, uuid, code):
self.strict_redis.set(f"captcha_code_{uuid}", code, 60)
def get_captcha_code(self, uuid):
return self.strict_redis.get(f"captcha_code_{uuid}")登录
前端 - 请求登录
javascript
layui.use(function () {
let form = layui.form;
let layer = layui.layer;
let captcha_uuid = "";
// 提交事件
form.on("submit(login-submit)", function (data) {
let field = data.field; // 获取表单字段值
// 显示填写结果,仅作演示用
field["captcha_uuid"] = captcha_uuid;
// 发送请求
$.ajax({
type: "POST",
contentType: "application/json",
url: "/api/v1/login",
data: JSON.stringify(field),
success: function (data) {
// 处理后端的响应数据
if (!data.code) {
layer.msg(data.msg, { icon: 1 });
} else {
layer.msg(data.msg, { icon: 2 });
}
},
});
return false;
});
});后端 - 验证登录
编写登录接口
python
@index_api.post("/login")
def login_api():
data = request.get_json()
captcha = data["captcha"]
captcha_uuid = data["captcha_uuid"]
mobile = data["mobile"]
password = data["password"]
redis_captcha = redis_store.get_captcha_code(captcha_uuid)
if redis_captcha != captcha:
return {"msg": "验证码错误", "code": -1}
q = db.select(UserORM)
q = q.where(UserORM.mobile == mobile)
user = db.session.execute(q).scalar()
if not user:
return {"msg": "用户不存在", "code": -1}
if not user.check_password(password):
return {"msg": "密码错误", "code": -1}
access_token = create_access_token(user)
response = make_response({
"code": 0,
"msg": "登录用户成功",
"access_token": "Bearer " + access_token
})
# 在 cookie 中设置 token
set_access_cookies(response, access_token)
return response权限校验
等了成功之后,需要颁发用户身份给前端。这里采用 flask-jwt-extended。
shell
poetry add flask-jwt-extendedpython
# filename: forum/extensions/init_jwt.py
from flask_jwt_extended import JWTManager
jwt_manager = JWTManager()
def register_jwt_callback():
from forum.orms.user import UserORM
@jwt_manager.user_identity_loader
def user_identity_lookup(user):
return user.id
@jwt_manager.user_lookup_loader
def user_lookup_callback(_jwt_header, jwt_data):
identity = jwt_data["sub"]
return UserORM.query.filter_by(id=identity).one_or_none()python
from .init_db import db, migrate
from .init_redis import redis_store
from .init_jwt import jwt_manager, register_jwt_callback
def register_extensions(app):
db.init_app(app)
migrate.init_app(app, db)
redis_store.init_app(app)
jwt_manager.init_app(app)
register_jwt_callback() 然后在视图中返回访问 token
python
@index_api.post("/login")
def login_api():
access_token = create_access_token(user)
return {
"code": 0,
"msg": "登录用户成功",
"access_token": "Bearer " + access_token
}然后再前端存储登录的
javascript
// 提交事件
form.on("submit(login-submit)", function (data) {
let field = data.field; // 获取表单字段值
// 显示填写结果,仅作演示用
field["captcha_uuid"] = captcha_uuid;
// 发送请求
$.ajax({
type: "POST",
contentType: "application/json",
url: "/api/v1/login",
data: JSON.stringify(field),
success: function (data) {
// 处理后端的响应数据
if (!data.code) {
layer.msg(data.msg, { icon: 1 });
localStorage.setItem("access_token", data.access_token);
setTimeout(function () {
location.reload();
}, 2000);
} else {
layer.msg(data.msg, { icon: 2 });
}
},
});
return false;
});最后,如果想要在 cookie 中使用 token,需要配置一个东西。
python
class BaseConfig:
JWT_TOKEN_LOCATION = ["headers", "cookies"] 登出
登录出功能只要在前端删除缓存的 access_token,然后刷新一下页面即可。
javascript
localStorage.removeItem("access_token");