5.1 用户认证(JWT 双令牌)
概述
JWT(JSON Web Token)是系统核心认证机制。采用双令牌设计:access_token 用于接口认证(2 小时),refresh_token 用于令牌刷新(7 天)。无状态、可水平扩展,配合 jti 黑名单实现可控的会话撤销。
双令牌机制
text
┌──────────┐ ┌──────────┐
│ 用户登录 │──── username ────►│ │
│ │──── password ────►│ auth │
│ │──── code/key ────►│ .Login()│
└──────────┘ └────┬─────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
access_token refresh_token user info
(2小时有效) (7天有效) (基础信息)两种令牌的区别
| 特性 | access_token | refresh_token |
|---|---|---|
| 用途 | 接口认证(每个 API 请求携带) | 刷新令牌(仅在 access_token 过期时使用) |
| 有效期 | 2 小时(7200 秒,jwt.accessTokenExpire) | 7 天(604800 秒) |
| claims.typ | access | refresh |
| 传递方式 | Authorization: Bearer <token> | POST /api/oauth2/token 请求体 |
| 校验方 | JWT 中间件 | logic/auth Refresh |
Claims 结构(utility/jwt,真实实现)
text
算法 HS256;密钥 jwt.secret;签发者 jwt.issuer(默认 easygoadmin)
Claims(golang-jwt/v5 + RegisteredClaims):
user_id / tenant_id / username / realname / typ(access|refresh)
RegisteredClaims:jti(随机)、iss、iat、exp(exp 必填,Parse 强校验)关键 API:
text
Sign(ctx, tokenType, userID, tenantID, username, realname) → (token, claims, err)
Parse(ctx, tokenStr) HS256 方法限定(防算法混淆)+ 签名/过期/签发者校验,
失败统一 401 "Token无效或已失效"
ExpireDuration(ctx, typ) 有效期(黑名单 TTL 计算用)令牌全生命周期
text
1. 登录
POST /api/login {username, password, code, key}
│ 签发 access_token + refresh_token
▼
前端存储(access 用于后续请求,refresh 用于续期)
2. 请求 API
GET /api/user/page
Authorization: Bearer <access_token>
│ JWT 中间件:Parse → 黑名单 Exists → 注入身份
▼
controller → service → logic → 响应
3. access 到期(前端响应拦截器检测 401)
POST /api/oauth2/token {grant_type: refresh_token, refresh_token: <refresh>}
│ 校验 typ=refresh → 旧 refresh 已黑?→ 轮换(旧黑签新)
▼
返回新令牌对(旧 access 自然过期,旧 refresh 入黑名单防重放)
4. 登出 / 强退
GET /api/logout 或 forceLogout
│ 当前 access jti 入黑名单(TTL=剩余有效期)
│ 强退额外拉黑 refresh jti(防被踢会话经 refresh 复活)
▼
MarkOffline:在线会话注销黑名单机制
text
键:easygoadmin:token:blacklist:{jti} = "1"
TTL:令牌剩余有效期(过期即令牌本身已失效,无需主动清理)
写入时机:
* 登出(Logout)
* refresh 轮换(旧 refresh 拉黑)
* RemoveToken 指定撤销(DELETE /api/oauth2/remove/:token)
* 在线用户强退(access + refresh 一并拉黑)
读取方:JWT 中间件每请求 Exists 判定,命中即 401令牌刷新与撤销接口
text
POST /api/oauth2/token(公开)Refresh:
* grant_type=password 或无 refresh_token → 兼容密码模式,复用 Login
* refresh_token 模式:
- Parse 校验 + typ 必须为 refresh(1008)
- 旧 refresh 已在黑名单 → 1008(防重放)
- 旧 refresh 入黑名单(轮换)
- 签发新 access+refresh,MarkOnline 刷新在线登记
- 返回同登录格式四字段
GET /api/oauth2/check/token 登录可用;JWT 中间件通过即 {valid:true, userId}
DELETE /api/oauth2/remove/:token 登录可用;解析指定 token 的 jti 入黑名单
GET /api/logout 当前 jti 入黑名单 + MarkOffline + 登录日志(log_type=2)登录响应格式(OAuth2 惯例,命名例外)
json
{
"access_token": "eyJhbGciOiJIUzI1NiIs…",
"refresh_token": "eyJhbGciOiJIUzI1NiIs…",
"expires_in": 7200,
"token_type": "Bearer"
}命名例外
这是全系统唯一使用下划线命名的响应(其余入出参一律驼峰)。前端按 data.access_token 读取,勿改驼峰。
安全设计要点
- HS256 +
WithValidMethods限定,防算法混淆攻击(none/RS256 降级) - exp 必填强校验;iss 与配置比对
- 双令牌分工明确:access 不能当 refresh 用(typ 校验)
- 黑名单 TTL 精确等于剩余有效期,存储无冗余
- 无服务端 Cookie 会话——会话固定攻击面不存在
- 配置项:
jwt.secret(生产必须改)、jwt.issuer、accessTokenExpire、refreshTokenExpire