Skip to content

5.2 认证流程实战 ​

概述

本节以登录闭环为主线,串起验证码、用户查找、状态校验、密码比对、令牌签发、登录日志、在线登记八个步骤,给出完整时序图与失败分支处理。对应代码在 internal/logic/auth.go。

登录时序图 ​

text
Client            Server/auth              Redis            DB
  │ GET /api/captcha    │                    │                │
  │────────────────────▶│ Generate ─────────▶│ SET captcha:uuid│
  │◀── {key, 图片base64} │                    │                │
  │ POST /api/login {username,password,code,key,tenantCode?}
  │────────────────────▶│ Verify ───────────▶│ GET+DEL captcha:uuid
  │                     │ findUser ──────────────────────────▶│ user(+tenant)
  │                     │ status==1?  mustLoadTenant ────────▶│ tenant(60s缓存)
  │                     │ bcrypt(password+salt) 比对           │
  │                     │ Sign(access) + Sign(refresh)        │
  │                     │ writeLoginLog(成功/失败) ──────────▶│ login_log
  │                     │ MarkOnline ─────────▶│ ZAdd online:jti:set
  │                     │                       │ SET online:info:{jti}
  │◀── {access_token, refresh_token, expires_in, token_type}   │

登录八步(POST /api/login) ​

入参:{username, password, tenantCode?, code?, key?}(每一步失败均写登录日志 log_type=1 status=1):

text
1) 提供 key 时强制验证码校验(captcha.Verify,一次性)
2) findUser:username 全局查找;带 tenantCode 先按 code 定位租户再过滤;
   多租户同名 → 优先平台租户(tenant_id=0),仍歧义 → 提示携带租户编码
3) 账号状态 status==1 校验(1002 账号禁用)
4) 租户状态与过期校验(mustLoadTenant,60s 缓存;1006 禁用 / 1005 过期)
5) 密码:crypto.VerifyPassword(bcryptHash, password, salt)
   —— bcrypt(password+salt) 双重加盐;IsBcrypt 识别旧 md5 数据(登录拒绝,
   需重置密码升级);salt 为空兼容直接比对
6) 签发双令牌(claims 含 user/tenant/username/realname)
7) 写登录日志成功(log_type=1 status=0,含 ip/location/os/browser/consume_time;
   code 参数脱敏为 ***)
8) MarkOnline 登记在线会话(zset + info 键)

失败分支与错误码 ​

失败点错误码提示
验证码错误/过期1004验证码错误
用户不存在1001用户不存在
账号禁用1002账号已禁用
密码错误1003密码错误
租户禁用1006租户已禁用
租户过期1005租户已过期

全部失败路径都写登录日志(status=1 + error 原因),供安全审计。

租户内登录 ​

text
场景:多个租户存在同名用户名
处理:登录时携带 tenantCode(租户编码)
  findUser:带 tenantCode → 先按 code 定位 tenant_id → 再按 username 过滤
  不带:全局查找;多租户同名时优先平台租户(tenant_id=0);仍歧义 → 提示携带租户编码

令牌生命周期状态图 ​

text
 Sign(access 2h)                 Refresh 轮换 / Logout / 强退
────────▶ 有效 ────────────────────────────────────────▶ 黑名单
    ▲             │  JWT中间件每请求: Parse + 黑名单Exists             │
    │             ▼                                                  ▼
    │          到期(exp) ──前端──▶ POST /api/oauth2/token      自然过期(TTL
    │                              (refresh_token 模式)         = 剩余有效期)自动清理
    └── 旧 refresh 已拉黑则 1008;否则旧 refresh 入黑名单 + 签发新对

前端对接要点 ​

text
1. 登录成功:access/refresh 存本地;后续请求头 Authorization: Bearer <access>
2. 响应拦截器:code=401 → 尝试 refresh(防并发刷新:单飞锁)→ 失败跳登录页
3. refresh 也 1008/401 → 清令牌跳登录
4. 登出:调 GET /api/logout 后清本地

实战调试 ​

sh
# 1. 拿验证码
curl http://127.0.0.1:8000/api/captcha

# 2. 登录(bypassCode 免验证码)
curl -X POST http://127.0.0.1:8000/api/login -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"123456","code":"618618","key":"<key>"}'

# 3. 带令牌调业务
curl http://127.0.0.1:8000/api/index/getUser -H "Authorization: Bearer <access_token>"

# 4. 刷新
curl -X POST http://127.0.0.1:8000/api/oauth2/token -H "Content-Type: application/json" \
  -d '{"grant_type":"refresh_token","refresh_token":"<refresh_token>"}'

# 5. 登出(令牌拉黑)
curl http://127.0.0.1:8000/api/logout -H "Authorization: Bearer <access_token>"

排障:登录成功但接口 401 → 检查是否把 refresh_token 当 access 用;登出后旧令牌仍可用 → 该实例是否内存降级(黑名单不跨实例)。

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