← [ BACK_TO_NOTES ]

Access Token / Refresh Token 认证实现流程

从登录、短期 Access Token、长期 Refresh Token、Server-side Session/Grant 到刷新、并发控制和失败收口,说明 Hybrid Token 认证的完整实现流程。

#howto #growing #backend #nodejs #jwt

[!info] related notes

Access Token / Refresh Token 认证实现流程

目标

这篇描述一种典型 Hybrid 认证链路:

长期登录生命周期
→ Server-side Session / Grant

普通 API 请求
→ short-lived Access Token

Access Token 续期
→ Refresh Token

它不是所有 Web 应用的默认答案。普通 Browser Web App 如果直接用 opaque Session Cookie 更简单,就没有必要为了“使用 JWT”额外增加刷新协议。

总体架构

                 ┌─────────────────────┐
                 │ Session / Grant Store│
                 │ user / revoke / TTL  │
                 └──────────┬──────────┘
                            │
Login / Refresh ────────────┤
                            │ issue
                            ▼
                    short-lived Access Token
                            │
                            ▼
                    Resource Server
                    local verify fast path

核心分工:

Session / Grant
→ 长期生命周期 Source of Truth

Access Token
→ 高频短期访问凭证

Refresh Token
→ 低频续期凭证,进入长期状态控制面

1. Login

客户端提交登录凭证:

email / username + password

服务端:

parse request
→ lookup user
→ verify password hash
→ check account state
→ create Session / Grant
→ create/bind Refresh Token
→ issue short-lived Access Token
→ return credentials / set cookie

顺序上,长期可撤销状态应该先成功建立,再把依赖它的凭证交给客户端;否则可能出现“凭证已发出,但服务器无法按设计控制其生命周期”的中间状态。

2. 普通 API 请求

客户端携带 Access Token:

Authorization: Bearer <access-token>

如果它是短期 JWT,Resource Server 可以:

verify signature
→ check exp / nbf
→ check iss / aud
→ read sub / scope
→ establish principal
→ run resource authorization

在典型 Hybrid fast path 中,这一步不需要每次查询 Session Store。

因此普通请求性能路径可以保持接近 Stateless。

3. 资源级 Authorization

Access Token 只能提供身份和粗粒度 claims,不能替代业务资源检查。

例如:

sub = user 42
scope = assessment:write

仍然不代表 User 42 可以修改任何 Assessment。

业务层还需要判断:

WHERE assessment.id = :id
  AND assessment.user_id = :authenticated_user_id

或使用更复杂的 Policy / membership 规则。

4. Access Token 过期

当 API 因 Access Token 过期返回认证失败,客户端进入刷新流程。

original request
→ Access Token expired
→ refresh once
→ get new Access Token
→ retry original request

Refresh Endpoint 不应该依赖旧 Access Token 才能工作;它自己的认证凭证就是 Refresh Token。

5. Refresh

Browser 场景常见:

Refresh Token
→ HttpOnly + Secure Cookie
→ browser auto-sends to /auth/refresh

服务端:

read Refresh Token
→ verify / hash lookup
→ find Session / Grant
→ check revoked / expired
→ check user state
→ rotate Refresh Token if used
→ issue new Access Token

这里才回到 Stateful 生命周期控制面。

6. Refresh Token Rotation

如果使用 rotation:

RT1
 ↓ refresh
AT2 + RT2

RT1 becomes invalid

若 RT1 之后再次出现:

possible copied credential / replay
→ revoke refresh family or Session

关键不是“生成新 Token”本身,而是:

consume old credential
+
create replacement

必须尽量形成原子 single-winner transition。

7. 并发 401 / Refresh Storm

一个页面往往同时有多个 API 请求。Access Token 恰好过期时,不能让每个 401 都独立刷新。

更合理:

request A gets 401
→ starts refresh

request B/C/D get 401
→ wait same refresh promise

refresh succeeds
→ all retry with new Access Token

客户端应实现 single-flight / refresh lock,而不是并发打多个 refresh 请求。

8. 如何避免 Refresh 请求递归触发刷新

旧实现常通过 X-Skip-Auth 自定义 Header 标记特殊请求,但这只是其中一种客户端实现技巧,并不是认证协议的一部分。

更干净的做法通常是二选一:

独立 Auth Client

apiClient
→ 自动注入 Access Token
→ 自动处理 401

authClient
→ login / refresh
→ 不经过普通 401 refresh interceptor

请求级内部配置

在 HTTP Client 自己的 config metadata 中标记:

skipAccessTokenInjection = true
skipAutoRefresh = true

这个标记只存在客户端代码内部,不需要真的发送给服务器,也就不需要增加 CORS Header。

核心原则只有一个:

刷新请求不能再次进入“Access Token 过期 → 自动刷新”的同一递归路径。

9. 浏览器存储策略

不要把“Access + Refresh 都放 localStorage”当默认答案。

一种常见边界:

Access Token
→ short-lived
→ memory

Refresh Token
→ long-lived
→ HttpOnly + Secure Cookie

页面 reload 后 Access Token 消失,可以通过 refresh/bootstrap 恢复。

如果系统使用 BFF 或纯 Server-side Session,则浏览器甚至不需要直接接触 Access Token。

10. Logout

Hybrid 架构中,Logout 不应该只清前端变量:

client clears access state
+
server revokes current Session / Refresh family
+
clear Refresh Cookie

“退出所有设备”则撤销用户所有 Session/Grant。

注意:如果已经签发的 Access Token 走完全本地验证,它可能继续有效到自身 exp。需要即时撤销时,可以:

  • 对敏感操作额外查实时状态;
  • 缩短 Access Token TTL;
  • 引入 denylist/introspection;
  • 或直接采用每请求 Session 验证。

11. Refresh 失败如何收口

Refresh 失败后不要无限循环:

refresh failed
→ stop retries
→ clear local authenticated state
→ clear invalid credential if appropriate
→ redirect / ask user to authenticate again

要区分网络暂时失败和明确的 invalid_grant / revoked / expired,避免把所有瞬时错误都当成永久登出。

12. Access Token TTL 怎么定

没有“必须 5 分钟”这一固定标准。

TTL 是以下因素的权衡:

shorter TTL
→ smaller stale/replay window
→ more refresh traffic

longer TTL
→ fewer refreshes
→ revoked/changed state may stay stale longer

根据系统风险、客户端环境、Refresh 可靠性和服务规模选择。

13. 是否一定要用 JWT

不一定。

Access Token
→ JWT or opaque

Refresh Token
→ JWT or opaque

JWT 的主要价值是在多 Resource Server 场景中允许本地验证自包含 claims。

如果每次请求本来就要查同一个 Session Store,那么直接使用 opaque Session Token 可能更简单。

最稳的架构表述

Hybrid 认证把长期登录生命周期放在服务端 Session/Grant 中,用短生命周期 Access Token 服务高频 API 请求;Access Token 过期后,客户端用 Refresh Token 回到 Auth Server,服务器检查 Session/Grant、撤销和用户状态后再签发新的短期 Access Token。普通请求可以无状态验证,Refresh/Logout/Revocation 则回到有状态控制面。

最短流程

Login
→ create Session / Grant
→ issue Access + Refresh

API
→ Access Token local verify
→ business authorization

Expired
→ Refresh Token
→ check server state
→ rotate / issue new Access

Logout
→ revoke Session / Refresh family

无提示执行检查

不看上文,画出并解释完整链路:

  1. Login 成功后,Server-side Session/Grant、Access Token、Refresh Token 分别承担什么职责?
  2. 普通 API 请求为什么只验证 Access Token 还不够,仍要做资源级 Authorization?
  3. Access Token 过期后,Refresh 请求为什么需要重新检查服务端状态?
  4. Refresh Token Rotation 要解决什么攻击窗口?为什么要处理 token family / reuse?
  5. 多个请求同时 401 时,如何避免 refresh storm?
  6. 为什么 Refresh 请求不能被普通 401 interceptor 递归触发自己?
  7. 设计一条 Logout 后旧 Access Token 仍有短暂 stale window 的收口策略。
创建于 2025/1/1 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本