bigywater.tech
Spring · 137

服务间认证:令牌透传

服务之间怎么传递用户身份?网关登录之后下游为什么还是 401?TokenRelay 到底转发了什么?第二跳要不要自己转发?令牌过期了谁来刷新?这篇用授权服务器、网关和两个资源服务器在 Spring Cloud Gateway 5.0.3 上走一遍。

401
网关登录成功但路由没有 TokenRelay 时,下游资源服务器的响应
jti 不变
TokenRelay 透传的是会话里同一个 access_token,多次请求 jti 相同
aud=gw
为网关客户端签发的令牌,下游服务照样接受,默认不检查受众

没有 TokenRelay

实测 · 本机执行输出
  浏览器访问 GET /api/me:跳转链 ['302', '302', '302', 'AS 登录', '302', '302', '401'](登录之后);最终 HTTP 401,A 回答:

登录态不会变成下游的 Authorization 头,下游 401。

TokenRelay

实测 · 本机执行输出
  登录后 GET /api/me:HTTP 200,A 回答:A 收到:sub=alice,scope=[read, openid],jti=a1e7c11a,aud=[gw],请求里是否带 Authorization 头=true
  再请求两次,A 收到的 jti(令牌的唯一标识):a1e7c11a / a1e7c11a(与第一次 相同)
  没有登录的浏览器直接请求 /api/me:HTTP 302,Location /oauth2/authorization/gw
  绕过网关直接请求 A 的 /me(不带令牌):HTTP 401,WWW-Authenticate 里带 resource_metadata = True

原样透传同一个令牌;未登录 302;下游不检查受众。

第二跳

实测 · 本机执行输出
  A → B,relay=true:A → B 收到:sub=alice,scope=[read, openid],jti=a1e7c11a,aud=[gw],请求里是否带 Authorization 头=true
  A → B,relay=false:A → 调用下游失败:Unauthorized:401 Unauthorized: [no body]

下游收到的头不会自动传给更下游,要显式转发。

自动刷新

实测 · 本机执行输出
  登录后第一次 A 收到的 jti=b395d43b;等 4 秒再请求一次,jti=a4cb4dc5,HTTP 200:令牌被自动刷新了(jti 变了),浏览器那边没有任何感知

令牌快过期时 TokenRelay 用 refresh_token 刷新。

分步演示

令牌透传
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。授权服务器是 spring-boot-starter-oauth2-authorization-server(机密客户端 gw,授权码模式,不要同意页,用户 alice / pw);Gateway 配 oauth2Login 和 TokenRelay;A、B 是 spring-boot-starter-oauth2-resource-server(issuer-uri,JWT);浏览器用 Python 手工带 Cookie
    • 没有验证:Feign / RestClient 的自动令牌拦截器(OAuth2AuthorizedClientManager);client_credentials 的服务间机器身份;令牌交换(Token Exchange)缩小受众与权限;在 A 里配 aud 校验后 aud=gw 的令牌是否被拒绝;Gateway 的 WebFlux 会话存放(内存 / Redis);登出与令牌撤销;多个网关实例共享登录态;「令牌快过期约 60 秒」的默认值(从现象推断)
    京ICP备2026067502号-1京公网安备11010502063487号