# 5.2 认证流程实战 :::tip 概述 本节以登录闭环为主线,串起验证码、用户查找、状态校验、密码比对、令牌签发、登录日志、在线登记八个步骤,给出完整时序图与失败分支处理。对应代码在 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 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":""}' # 3. 带令牌调业务 curl http://127.0.0.1:8000/api/index/getUser -H "Authorization: Bearer " # 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":""}' # 5. 登出(令牌拉黑) curl http://127.0.0.1:8000/api/logout -H "Authorization: Bearer " ``` 排障:登录成功但接口 401 → 检查是否把 refresh_token 当 access 用;登出后旧令牌仍可用 → 该实例是否内存降级(黑名单不跨实例)。