Skip to content

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_tokenrefresh_token
用途接口认证(每个 API 请求携带)刷新令牌(仅在 access_token 过期时使用)
有效期2 小时(7200 秒,jwt.accessTokenExpire)7 天(604800 秒)
claims.typaccessrefresh
传递方式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

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