OAuth2 refresh_token刷新时生成新令牌而非复用现有令牌问题
问题根源
这是该OAuth2组件默认启用刷新令牌轮换机制的默认行为:默认配置下刷新令牌仅设置了相对过期时间,即每次签发新refresh_token时,过期时间都从当前签发时刻重新计算,没有绑定整个授权会话的绝对过期上限。只要用户在单次refresh_token过期前触发刷新,就能无限续期拿到有效令牌,导致永久登录状态。
可落地方案
方案1:配置刷新令牌绝对过期时间(推荐,符合OAuth2安全最佳实践)
无需修改核心业务代码,直接在组件配置中新增刷新令牌绝对TTL限制即可,注意区分两个时间配置的作用:
- 单次refresh_token相对有效期:指单个refresh_token从签发到失效的时长,用于控制用户多久需要发起一次刷新请求
- 授权会话绝对有效期:指从用户第一次授权成功开始,整个登录会话的最长存活时间,无论中间刷新多少次,到期后所有令牌强制失效
以Symfony框架下的YAML配置为例,其他框架适配的配置项逻辑完全一致:
league_oauth2_server: # access_token有效期,示例设为1小时 access_token_ttl: PT1H # 单个refresh_token相对有效期,示例设为7天 refresh_token_ttl: P7D # 授权会话绝对最长有效期,示例设为30天 options: rotate_refresh_tokens: true revoke_old_refresh_tokens: true # 核心配置:强制所有轮换生成的refresh_token过期时间不超过首次授权的绝对时间点 refresh_token_absolute_ttl: P30D
配置生效后,即使用户每次都在refresh_token到期前主动刷新,新生成的refresh_token过期时间也不会超出首次授权后30天的上限,到期后所有令牌自动失效,用户必须重新走登录流程。
方案2:自定义签发逻辑,联动自身会话规则
如果需要更灵活的过期控制(比如和用户的「记住我」选项、账号风险等级、管理员强制下线操作联动),可以通过组件提供的事件扩展点自定义逻辑:
- 监听刷新令牌签发事件
- 事件触发时,取出当前授权对应的用户标识、首次授权时间
- 如果当前时间距离首次授权已经超过你设定的会话最长时长,直接拒绝签发新令牌,同时作废该用户名下所有有效refresh_token
- 未超时则正常签发新令牌,也可根据业务规则动态调整本次签发的令牌有效期
方案3:关闭刷新令牌轮换(仅适合内部低安全场景,不推荐)
如果不需要刷新令牌轮换机制,可以直接关闭该功能,此时refresh_token只会在首次授权时签发一次,后续刷新access_token时不会生成新的refresh_token,直到这个固定的refresh_token到达预设过期时间自动失效:
league_oauth2_server: options: rotate_refresh_tokens: false
风险提示:该方案下refresh_token一旦被窃取,可在有效期内被无限次使用,存在明显安全隐患,生产环境公网服务不建议使用。
校验方法
配置完成后按以下步骤验证即可:
- 首次调用授权接口获取refresh_token,记录其过期时间点
- 间隔数分钟后用该refresh_token调用刷新接口,拿到新的refresh_token
- 检查新refresh_token的过期时间,应和首次获取的refresh_token的绝对过期点完全一致,不会向后顺延
- 到达绝对过期时间后,使用任意轮换生成的refresh_token调用刷新接口,都会返回
invalid_grant错误,要求用户重新登录
内容的提问来源于stack exchange,提问作者Jigar Pancholi
相关产品推荐
相关产品推荐

