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 用;登出后旧令牌仍可用 → 该实例是否内存降级(黑名单不跨实例)。