Skip to content

本章概要

登录 → Token 获取 → 权限校验 → Token 刷新 → 退出登录的完整认证链路实战说明。

认证流程实战

本章将登录 → Token 获取 → 权限校验 → Token 刷新 → 退出登录的完整认证链路串联说明,帮助开发者理解端到端的认证机制。

认证架构总览

┌──────────┐     ①获取验证码      ┌──────────┐
│          │ ──────────────────> │          │
│  前端    │                     │  后端    │
│  Vue3    │  ②登录(账号+密码+验证码) │  FastAPI │
│          │ ──────────────────> │          │
│          │  ③返回 Token        │          │
│          │ <────────────────── │          │
│          │                     │          │
│          │  ④携带 Token 请求    │          │
│          │ ──────────────────> │          │
│          │  ⑤返回业务数据       │          │
│          │ <────────────────── │          │
│          │                     │          │
│          │  ⑥刷新 Token        │          │
│          │ ──────────────────> │          │
│          │  ⑦返回新 Token       │          │
│          │ <────────────────── │          │
│          │                     │          │
│          │  ⑧退出登录           │          │
│          │ ──────────────────> │          │
└──────────┘                     └──────────┘

第 1 步:获取验证码

登录前需先获取图形验证码,防止暴力破解。

请求

GET /api/captcha

响应

json
{
    "code": 0,
    "msg": "操作成功",
    "data": {
        "captcha": "data:image/png;base64,iVBORw0KGgo...",
        "key": "captcha:a3f8b2c1d4e5"
    }
}
  • captcha:Base64 编码的验证码图片,前端直接赋值给 <img>src
  • key:验证码唯一标识,登录时需回传

前端代码示例

typescript
// 获取验证码
const { data } = await getCaptchaApi();
captchaImg.value = data.captcha;
captchaKey.value = data.key;

后端实现

python
# src/api/v1/endpoints/auth.py
@router.get('/captcha')
async def captcha(request: Request):
    result = await generate_captcha(request)
    id_key = result['idKey']
    data = result['data']
    img_str = "data:image/png;base64," + data
    return R.ok({"captcha": img_str, "key": id_key})

验证码通过 Redis 存储,Key 格式为 captcha:{uuid},默认有效期 300 秒。

第 2 步:用户登录

携带账号、密码、验证码和 Key 发起登录请求。

请求

POST /api/login
Content-Type: application/json

{
    "username": "admin",
    "password": "123456",
    "code": "a3Kp",
    "key": "captcha:a3f8b2c1d4e5"
}

登录表单字段

字段类型必填说明
usernamestr登录账号(1-20 字符)
passwordstr登录密码(6-128 字符)
codestr验证码(长度由 CAPTCHA_LENGTH 配置)
keystr验证码 Key

成功响应

json
{
    "code": 0,
    "msg": "登录成功",
    "data": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "must_change_password": false
    },
    "ok": true
}
  • access_token:JWT 令牌,后续请求需携带
  • must_change_password:是否需要修改密码(检测到默认密码时为 true

失败响应

json
// 验证码错误
{"code": 1, "msg": "验证码不正确", "data": null, "ok": false}

// 账号或密码错误
{"code": 1, "msg": "用户名或密码不正确", "data": null, "ok": false}

// 账号被锁定
{"code": 1, "msg": "登录失败次数过多,请280秒后再试", "data": null, "ok": false}

登录流程详解

1. 检查是否被锁定(IP+用户名维度)
   └─ 锁定中 → 返回剩余等待秒数

2. 验证码校验(Lua 原子消费,防重放)
   └─ 不正确 → 记录失败次数

3. 查询用户(同步 DB 查询放入线程池)
   └─ 不存在 → 记录失败次数

4. 比对密码(bcrypt CPU 密集,放入线程池)
   └─ 不匹配 → 记录失败次数

5. 登录成功
   ├─ 清除失败记录
   ├─ 检测是否为默认密码
   ├─ 生成 JWT Token
   └─ 返回 Token

防暴力破解机制

维度策略配置项
失败计数IP+用户名维度,Redis 原子递增LOGIN_FAIL_MAX=5
锁定机制达到上限后锁定LOGIN_LOCK_SECONDS=300
验证码防刷窗口期内限制获取次数CAPTCHA_RATE_LIMIT=10
验证码消费Lua 原子消费,防重放-

第 3 步:携带 Token 请求业务接口

登录成功后,前端将 Token 存入 localStorage,后续所有请求自动携带。

请求头格式

Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

前端自动携带

typescript
// 请求拦截器(自动添加 Token)
axios.interceptors.request.use(config => {
    const token = localStorage.getItem('access_token');
    if (token) {
        config.headers['Authorization'] = `Bearer ${token}`;
    }
    return config;
});

Token 解析流程

请求进入 → login_required 依赖
  ├─ 提取 Authorization 头
  ├─ 解析 JWT Token
  │   ├─ 验证签名(HS256 + JWT_SALT)
  │   ├─ 验证过期时间
  │   └─ 检查黑名单(Redis)
  ├─ 注入 request.state
  │   ├─ user_id
  │   ├─ username
  │   └─ realname
  └─ 业务代码通过 get_user_id(request) 获取用户信息

第 4 步:权限校验

每个业务接口通过 @permission_required 装饰器校验权限。

权限校验流程

请求到达 → permission_required("sys:user:add")
  ├─ 用户 ID = 1(admin)→ 跳过权限校验,直接放行
  └─ 其他用户
      ├─ 查询用户角色
      ├─ 查询角色权限列表
      ├─ 匹配权限字符串
      │   ├─ 匹配 → 放行
      │   └─ 不匹配 → 返回 "权限不足"
      └─ 结果缓存(Redis,菜单变更时失效)

权限字符串格式

sys:{module}:{action}

示例:sys:user:addsys:role:pagesys:article:delete

第 5 步:Token 刷新

Token 支持无感刷新,刷新时执行令牌轮换(旧 Token 立即失效)。

刷新请求

POST /api/v1/refreshToken
Authorization: Bearer <old_token>

刷新流程

1. 验证原令牌有效性
2. 检查是否已注销(黑名单)
3. 移除 JWT 标准字段(exp/iat/nbf),保留业务数据
4. 生成新令牌
5. 旧令牌加入黑名单
6. 返回新令牌

前端无感刷新

typescript
// 响应拦截器:检测 Token 过期
axios.interceptors.response.use(
    response => response,
    async error => {
        if (error.response?.status === 401) {
            // 尝试刷新 Token
            const newToken = await refreshToken();
            if (newToken) {
                // 用新 Token 重试原请求
                error.config.headers['Authorization'] = `Bearer ${newToken}`;
                return axios(error.config);
            }
            // 刷新失败,跳转登录页
            router.push('/login');
        }
        return Promise.reject(error);
    }
);

第 6 步:退出登录

退出时将当前 Token 加入 Redis 黑名单,使其立即失效。

请求

GET /api/v1/logout
Authorization: Bearer <token>

响应

json
{
    "code": 0,
    "msg": "注销成功",
    "data": null,
    "ok": true
}

退出流程

1. 提取当前 Token
2. 计算 Token 的 SHA256 指纹
3. 存入 Redis 黑名单(TTL = Token 剩余有效期)
4. 记录退出日志
5. 返回成功

Token 黑名单机制

  • 存储方式:Redis,Key 格式 token:blacklist:{sha256_fingerprint}
  • 过期策略:TTL 与 Token 剩余有效期一致,自动清理
  • 故障降级:Redis 不可用时 fail-open 放行(避免全站不可用)

完整时序图

前端                    后端                    Redis               数据库
 │                       │                       │                   │
 │  ① GET /captcha       │                       │                   │
 │ ────────────────────> │  生成验证码            │                   │
 │                       │ ────────────────────> │  存储 captcha key  │
 │  返回验证码图片+key    │ <──────────────────── │                   │
 │ <──────────────────── │                       │                   │
 │                       │                       │                   │
 │  ② POST /login        │                       │                   │
 │  (username+pwd+code)  │                       │                   │
 │ ────────────────────> │  ① 检查锁定           │                   │
 │                       │ ────────────────────> │  查询 lock key     │
 │                       │ <──────────────────── │                   │
 │                       │  ② 验证码校验          │                   │
 │                       │ ────────────────────> │  Lua 原子消费      │
 │                       │ <──────────────────── │                   │
 │                       │  ③ 查询用户            │                   │
 │                       │ ─────────────────────────────────────────> │
 │                       │ <───────────────────────────────────────── │
 │                       │  ④ 比对密码            │                   │
 │                       │  ⑤ 生成 JWT Token      │                   │
 │  返回 Token            │                       │                   │
 │ <──────────────────── │                       │                   │
 │                       │                       │                   │
 │  ③ GET /api/v1/user/page                      │                   │
 │  Authorization: Bearer xxx                    │                   │
 │ ────────────────────> │  解析 Token            │                   │
 │                       │  检查黑名单            │                   │
 │                       │ ────────────────────> │  查询 blacklist    │
 │                       │ <──────────────────── │                   │
 │                       │  校验权限              │                   │
 │                       │  查询业务数据          │                   │
 │                       │ ─────────────────────────────────────────> │
 │  返回业务数据          │ <───────────────────────────────────────── │
 │ <──────────────────── │                       │                   │
 │                       │                       │                   │
 │  ④ GET /logout        │                       │                   │
 │ ────────────────────> │  Token 加入黑名单      │                   │
 │                       │ ────────────────────> │  SET blacklist     │
 │  返回注销成功          │ <──────────────────── │                   │
 │ <──────────────────── │                       │                   │

配置项

环境变量默认值说明
JWT_SALT无(必填)JWT 签名密钥,至少 32 字节
JWT_EXPIRE_MINUTES20Token 过期时间(分钟)
LOGIN_FAIL_MAX5登录失败次数上限
LOGIN_LOCK_SECONDS300登录锁定时间(秒)
CAPTCHA_LENGTH6验证码长度
CAPTCHA_EXPIRE300验证码有效期(秒)

总结

认证流程覆盖了从获取验证码到退出登录的完整链路,核心安全机制包括:

  • bcrypt 密码哈希 + 随机盐
  • JWT HS256 签名 + 过期校验
  • Redis 验证码防重放
  • IP+用户名维度的登录锁定
  • Token 黑名单即时失效
  • Redis 不可用时 fail-open 降级

小蚂蚁云团队 · 提供技术支持