🌐 语言: 中文版 | English Version
在一次排查会话超时问题时,我们遇到了一个很典型的现象:前端已经提示会话失效,但用户重新进入授权流程后,并没有再次看到登录页,而是直接回到了系统。前后端最开始对这个现象的解释完全对不上,后来把链路拆开看,才发现问题不在某一个配置值,而在于大家说的“登录态”本来就不是同一层东西。
TL;DR
这篇文章默认读者已经熟悉 OAuth2/OIDC 的基本流程,重点放在生产环境里最容易混淆的超时边界。
OAuth2/OIDC 里的“登录态”通常不是一个时间,而是几类时间共同作用:
authorization code决定授权码多久失效access token决定当前令牌还能调用 API 多久refresh token决定应用还能静默续期多久- SSO 浏览器会话决定再次进入授权流程时是否还需要输入密码
- 服务端会话标识决定已签发令牌能否被主动撤销
排障时,把这几层拆开看,比只围绕某一个 TTL 反复讨论更有效。
一、问题通常出在哪里
当时看到的现象很具体:
- 用户正在使用前端应用
- 一段时间后,前端提示会话过期
- 用户点击重新登录
- 浏览器跳转到授权服务器后,没有出现登录页
- 授权完成后,用户又直接回到了系统
这个现象通常说明两件事同时成立:
- 前端本地持有的
access token已经过期,或者已经被前端判定为即将过期 - 授权服务器的浏览器会话仍然有效,所以授权服务器还认得这个用户
所以,应用提示的“需要重新登录”,很多时候只是“需要重新获取一组 token”,并不等于“用户必须重新输入账号密码”。
这个区别如果不先说清楚,后面的排障很容易跑偏。
二、先分清协议层和工程层
在标准的 OAuth2/OIDC 授权码流程里,核心角色只有四个:
- 用户
- 客户端应用
- 授权服务器
- 资源服务器
一个简化流程如下:
sequenceDiagram
participant User as User
participant Client as Client App
participant AS as Authorization Server
participant RS as Resource Server
User->>Client: 打开应用
Client->>AS: 跳转 /authorize
AS->>User: 登录并确认授权
AS-->>Client: 回调 redirect_uri?code=...
Client->>AS: 用 code 换 token
AS-->>Client: 返回 access token / refresh token
Client->>RS: 携带 access token 调用 API
RS-->>Client: 返回业务数据
协议层主要定义的是:
- 如何发起授权
- 如何交换 token
- 如何校验 token
但到了生产环境,系统通常还会叠加一些工程实现:
- API Gateway 统一做 token 校验
- 授权服务器用浏览器 session 保存登录记忆
- 服务端引入会话标识,支持 logout 后令牌立即失效
- 前端为了规避边界抖动,会提前几十秒把 token 判定为过期
这些机制都很常见,但它们并不属于同一个层次。把协议概念和工程策略混在一起讨论,会直接影响对“登录态”的判断。
三、最开始我们是怎么误判的
这类问题在现场里很容易先走到一个错误方向:先去改 access token 的 TTL。
原因也不复杂。前端提示会话失效,最直觉的理解就是 token 太短了;而且从现象上看,改长一点似乎就能缓解问题。
但这条思路通常只能解释一部分表象,解释不了下面这些情况:
- 为什么 token 过期后,重新授权时不需要再次输入密码
- 为什么有些请求会先收到
401,但页面并没有真正退出 - 为什么 logout 之后,某些旧 token 还能短时间继续访问
也就是说,单改 access token TTL 最多只能影响“前端多快感知失效”,并不能覆盖“是否还能静默续期”“是否还要重新输密码”“旧 token 能不能立刻失效”这些问题。
把这几个边界拆开之后,后面的现象才会变得一致。
四、一个更实用的心智模型:三层门票
把登录态理解成三层门票,排障会简单很多。
1. access token
这是访问资源服务器时直接带上的门票。
- 生命周期通常较短
- 暴露面最大
- 过期后最直接的表现是 API 返回
401
2. refresh token
这是应用用来换新 access token 的门票。
- 生命周期通常比 access token 长
- 不应该高频出现在业务请求里
- 它决定应用还能不能无感续期
3. SSO 会话
这一层通常又分成两部分:
- 浏览器侧的登录会话,比如
JSESSIONID这类 cookie - 服务端侧的会话标识,用于撤销、踢出、单点退出等治理能力
这两部分经常同时出现,但职责并不相同:
- 浏览器会话决定再次进入
/authorize时是否还需要输入密码 - 服务端会话标识决定已签发的 token 是否还能被继续接受
五、几个最容易混淆的时间参数
| 参数 | 所属层级 | 过期后的表现 | 常见建议 |
|---|---|---|---|
authorization_code TTL |
授权码 | 用 code 换 token 失败 | 短,通常 5 分钟左右 |
access_token TTL |
API 访问令牌 | 调 API 失败或前端判定失效 | 短,通常 10 到 15 分钟 |
refresh_token TTL |
续期令牌 | 无法静默续期,需要重新授权 | 中长,按应用风险分层 |
| SSO 浏览器会话超时 | 授权服务器登录记忆 | 再次授权时重新看到登录页 | 与平台会话策略保持一致 |
| 服务端会话标识 TTL | 撤销与登出治理 | 旧 token 是否还能被立即拦截 | 与会话治理策略保持一致 |
前端 skewSeconds |
本地提前过期判断 | token 会被提前一点判定失效 | 保持很小,例如 30 秒 |
5.1 authorization code
授权码是一次性的短期凭证,主要用于前端回调后向授权服务器换取 token。它的职责很单一,通常不参与“长会话”讨论。
把这个时间调长,通常既不能改善用户体验,也会扩大暴露窗口。
5.2 access token
这是用户体感最明显的一层。
- 时间太短,前端容易频繁出现未登录、接口重试、边界抖动
- 时间太长,泄露后的风险窗口会明显扩大
对大多数后台系统来说,10-15 分钟 往往比几分钟更平衡。
5.3 refresh token
很多“7 天免登录”“30 天持续可用”的诉求,本质上应该由 refresh token 来承担,而不是把 access token 直接拉长到几天。
短 access token 配合较长 refresh token,一般比长期 access token 更稳。
5.4 SSO 浏览器会话
这一层决定的是:用户再次走授权流程时,授权服务器是否还记得这个浏览器已经登录过。
所以会出现这样的情况:
- 应用本地 token 已经过期
- 用户重新发起授权
- 授权服务器检查到浏览器会话还有效
- 于是直接签发新的授权结果,用户无需再次输入密码
这不是绕过认证,而是 SSO 的正常体验。
5.5 服务端会话标识
很多生产系统会在 token 之外,再维护一个服务端会话标识,比如 sid 之类的 claim 或会话键。它的目的不是替代 token,而是补足“主动撤销”能力。
典型用途包括:
- 用户主动退出后,旧 token 立刻失效
- 管理员踢出某个会话
- 密码修改后强制旧会话下线
- 同浏览器多标签页共享一套失效状态
没有这层机制时,系统往往只能等 token 自然过期。
如果只讲概念,这一层还是容易显得有点虚。用一个最小实现示意会更直接:
1 | // 签发 token 时,把 sid 一起写入 claim |
这个做法的重点不在 Redis 本身,而在于给“主动撤销”加了一个服务端抓手。只靠 JWT 的 exp,系统通常只能等它自然过期。
六、为什么前端和后端经常对不上
登录态问题之所以容易争论,是因为不同角色观察到的是不同层:
- 前端关注本地 token 是否还可用
- 网关或资源服务器关注 bearer token 是否能通过校验
- 授权服务器关注浏览器会话是否仍然存在
- 安全治理关注旧会话是否能被主动撤销
如果这几层没有分开描述,团队内部就很容易出现两种看起来互相矛盾、但各自都没错的说法:
- “明明过期了,为什么又直接进去了?”
- “明明已经退出了,为什么旧 token 还能再调一次接口?”
前一句通常在说 SSO 浏览器会话仍有效,后一句通常在说服务端撤销链路没有兜住。
七、长会话不是只改一个 TTL
如果某个应用希望做到“几天内持续可用”,通常至少要同时满足下面几件事。
7.1 前端真的接入了 refresh token 续期
如果 access token 一过期,前端就直接清状态并跳回登录页,那么即使 refresh token 配到了 7 天,也不会改善体验。
7.2 退出登录时,服务端也能撤销会话
仅清理前端本地存储并不够。只要旧 token 还在有效期内,就可能继续被网关或资源服务器接受。长会话场景里,这一点尤其重要。
7.3 敏感事件要支持强制失效
例如:
- 修改密码
- 用户被禁用
- 权限发生重大变更
- 管理员踢出会话
这些场景不应该单纯依赖自然过期。
7.4 长期 refresh token 最好采用轮换策略
如果 refresh token 可以反复复用直到自然过期,一旦泄露,风险窗口会很长。
轮换策略的思路是:每次刷新后,让旧 refresh token 立即失效,并签发新的 refresh token。这样更适合长会话场景。
八、一个更稳的时间配置思路
下面是一组比较常见的参考分层:
| 场景 | access token |
refresh token |
浏览器会话 |
|---|---|---|---|
| 普通后台系统 | 10-15m |
8-12h |
半天或一个工作日 |
| 低风险长会话应用 | 10-15m |
7d |
按登录策略评估 |
| 极少数白名单应用 | 10-15m |
30d |
必须配套撤销与风控 |
这里有两个重点:
- 长体验优先通过
refresh token实现 access token仍然保持短期
如果直接把 access token 拉到数小时、数天,泄露后的影响面会明显变大,而且很多系统本身并没有配套的即时撤销能力。
九、排障时按什么顺序看
碰到“重新登录”“自动续期失败”“退出后仍可访问”这类问题时,按下面顺序看通常更高效:
- 先看
access token的exp - 再看前端是否真的执行了 refresh token 续期
- 再确认
refresh token本身是否还有效,是否采用轮换 - 再看授权服务器的浏览器会话是否仍然存在
- 最后看服务端撤销标识是否仍有效,logout 链路是否真正删除了它
这个顺序能快速区分:
- 是 API 门票过期了
- 是续期链路没接好
- 还是 SSO 会话还活着
- 或者撤销治理没做完整
十、一个足够实用的判断公式
如果只保留一句话,可以直接记这组边界:
- 是否还能调用 API,看
access token - 是否还能静默续期,看
refresh token - 是否还需要重新输入密码,看 SSO 浏览器会话
- 是否能让旧 token 立刻失效,看服务端会话撤销机制
这几个边界理顺之后,很多看起来矛盾的现象都会变得非常直接。
十一、结语
OAuth2/OIDC 本身并不复杂,真正容易混乱的是生产系统把 token、浏览器会话、网关校验、服务端撤销叠在了一起。把这些机制统称为一个“登录态”去讨论,结论很容易跑偏。
更稳的做法通常是:
- 用短期
access token控制 API 访问风险 - 用
refresh token提供持续体验 - 用浏览器会话承载 SSO 登录记忆
- 用服务端撤销机制处理 logout、踢人和强制下线
这几层边界清楚之后,前端、后端和安全侧在讨论超时策略时会轻松很多。