You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway过期Access Token刷新方案及问题求助

实现Spring Cloud Gateway自动刷新过期Access Token的可行方案

方案一:自定义GatewayFilter实现Token检查与刷新

直接自定义网关过滤器,手动把控Token校验、刷新的全流程,适配Azure AD的特性:

  • 步骤1:获取存储的Refresh Token
    从网关认证上下文(如SecurityContextRepository)中取出当前用户的Refresh Token——Azure AD授权码流程中,认证成功后会返回该Token,需提前存储(可存在Redis或网关会话中)。
  • 步骤2:校验Access Token有效性
    解析Access Token的exp字段判断是否过期;或调用Azure AD的令牌验证端点https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/introspect验证Token状态,请求需携带客户端ID和密钥。
  • 步骤3:执行Token刷新逻辑
    若Token过期,向Azure AD令牌端点https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token发送POST请求,参数包含grant_type=refresh_token、refresh_token=xxx、client_id=xxx、client_secret=xxx(机密客户端需提供)、scope=xxx(与授权时一致)。
  • 步骤4:更新Token并转发请求
    拿到新的Access Token和Refresh Token后,更新存储的Token信息,再将新Token放入Authorization: Bearer {newToken}请求头,转发给下游服务。

核心代码片段示例:

public class TokenRefreshGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return exchange.getPrincipal()
                .cast(OAuth2AuthenticationToken.class)
                .flatMap(authToken -> {
                    OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
                            authToken.getAuthorizedClientRegistrationId(),
                            authToken.getName()
                    );
                    OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
                    
                    if (accessToken.isExpired()) {
                        return refreshToken(authorizedClient)
                                .flatMap(newAuthorizedClient -> {
                                    authorizedClientService.saveAuthorizedClient(newAuthorizedClient, authToken);
                                    exchange.getRequest().mutate()
                                            .header("Authorization", "Bearer " + newAuthorizedClient.getAccessToken().getTokenValue())
                                            .build();
                                    return chain.filter(exchange);
                                });
                    } else {
                        return chain.filter(exchange);
                    }
                });
    }

    private Mono<OAuth2AuthorizedClient> refreshToken(OAuth2AuthorizedClient authorizedClient) {
        WebClient webClient = WebClient.create();
        return webClient.post()
                .uri("https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", "your-tenant-id")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData("grant_type", "refresh_token")
                        .with("refresh_token", authorizedClient.getRefreshToken().getTokenValue())
                        .with("client_id", authorizedClient.getClientRegistration().getClientId())
                        .with("client_secret", authorizedClient.getClientRegistration().getClientSecret())
                        .with("scope", String.join(" ", authorizedClient.getClientRegistration().getScopes())))
                .retrieve()
                .bodyToMono(OAuth2AccessTokenResponse.class)
                .map(tokenResponse -> {
                    OAuth2AccessToken newAccessToken = new OAuth2AccessToken(
                            OAuth2AccessToken.TokenType.BEARER,
                            tokenResponse.getAccessToken().getTokenValue(),
                            tokenResponse.getIssuedAt(),
                            tokenResponse.getExpiresAt()
                    );
                    OAuth2RefreshToken newRefreshToken = tokenResponse.getRefreshToken() != null ?
                            new OAuth2RefreshToken(tokenResponse.getRefreshToken().getTokenValue(), tokenResponse.getIssuedAt()) :
                            authorizedClient.getRefreshToken();
                    return new OAuth2AuthorizedClient(
                            authorizedClient.getClientRegistration(),
                            authorizedClient.getPrincipalName(),
                            newAccessToken,
                            newRefreshToken
                    );
                });
    }

    @Override
    public int getOrder() {
        return -1; // 确保在TokenRelay之前执行
    }
}

方案二:配置Spring Security OAuth2 Client自动刷新机制

Spring Security OAuth2 Client原生支持Token自动刷新,配合Gateway的TokenRelay即可实现需求:

  • 配置要点
    1. 在网关application.yml中开启OAuth2 Client自动刷新:
      spring:
        security:
          oauth2:
            client:
              registration:
                azure-ad:
                  client-id: your-client-id
                  client-secret: your-client-secret
                  authorization-grant-type: authorization_code
                  redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
                  scope: openid,profile,email,api://your-api-scope/.default
              provider:
                azure-ad:
                  issuer-uri: https://login.microsoftonline.com/your-tenant-id/v2.0
            authorized-client-service:
              redis:
                enabled: true # 集群部署时建议用Redis存储AuthorizedClient
      
    2. 确保OAuth2AuthorizedClientService配置正确(集群场景用Redis分布式存储,避免Token信息不一致)。
    3. 使用TokenRelayGatewayFilterFactory,此时Spring Security会自动在Access Token过期时调用Refresh Token刷新,TokenRelay负责转发新Token。
    • 注:若此前TokenRelayWithTokenRefreshGatewayFilterFactory出错,大概率是Azure AD端点适配问题,优先尝试此方案。

方案三:采用Azure AD的On-Behalf-Of(OBO)流程

如果下游服务也需调用Azure AD保护的API,OBO流程更适配:

  • 流程说明
    1. 网关收到用户的Access Token后,向Azure AD令牌端点发送OBO请求,参数包含grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer、assertion=用户的Access Token、scope=下游API的scope、client_id、client_secret。
    2. Azure AD返回针对下游服务的专属Access Token,网关将该Token转发给下游。
    3. 当OBO Token过期时,网关可再次发起OBO请求,只要用户的Refresh Token有效,就能获取新的OBO Token。
  • 优势:无需存储用户的Refresh Token,直接用用户Token换取下游服务的Token,安全性更高,且Azure AD自动管理Token生命周期。

通用注意事项

  • 无论采用哪种方案,需确保Refresh Token的有效性:Azure AD的Refresh Token存在过期时间,且多次刷新后旧Token会失效,需及时更新存储的Token。
  • 网关集群部署时,必须使用分布式OAuth2AuthorizedClientService(如Redis实现),避免节点间Token信息不一致。

内容的提问来源于stack exchange,提问作者dsreddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 20:15:42