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

能否用Spring Cloud Gateway与Spring Authorization Server实现无状态OAuth2授权码模式?

可以通过Spring Cloud Gateway + Spring Authorization Server实现该需求

核心逻辑

要实现无状态的授权码流程,核心是禁用网关会话存储,通过自定义回调处理逻辑直接将令牌返回给客户端,而非依赖Session保存认证信息。同时让网关以OAuth2客户端身份对接Spring Authorization Server,完成授权码的获取与令牌交换。

具体实现步骤

1. 依赖配置

添加必要的Maven依赖,确保网关具备OAuth2客户端能力,同时引入授权服务器组件(若自行搭建):

<!-- Spring Cloud Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- OAuth2 Client 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- Spring Authorization Server(自行搭建授权服务器时需引入) -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
</dependency>

2. 网关OAuth2客户端配置

在application.yml中配置客户端信息,指向你的Spring Authorization Server:

spring:
  security:
    oauth2:
      client:
        registration:
          spring: # 与示例中的客户端标识对应
            client-id: your-client-id
            client-secret: your-client-secret
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/spring" # 网关回调地址
            scope: openid,profile
        provider:
          spring:
            authorization-uri: http://auth-server:9000/oauth2/authorize # 授权服务器授权端点
            token-uri: http://auth-server:9000/oauth2/token # 令牌交换端点
            user-info-uri: http://auth-server:9000/userinfo
            user-name-attribute: sub
  cloud:
    gateway:
      routes:
        - id: login-redirect
          uri: no://op
          predicates:
            - Path=/login
          filters:
            - RedirectTo=302, /oauth2/authorization/spring # 访问/login自动跳转到授权入口

3. 自定义令牌返回逻辑(实现无状态)

默认OAuth2客户端会将令牌存入Session,我们需要自定义认证成功处理器,直接将令牌以JSON形式返回给客户端,同时禁用Session:

自定义认证成功处理器

@Component
public class StatelessTokenHandler implements AuthenticationSuccessHandler {

    private final OAuth2AuthorizedClientService clientService;

    public StatelessTokenHandler(OAuth2AuthorizedClientService clientService) {
        this.clientService = clientService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
        OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) auth;
        OAuth2AuthorizedClient authorizedClient = clientService.loadAuthorizedClient(
                oauthToken.getAuthorizedClientRegistrationId(),
                oauthToken.getName());

        // 组装令牌响应内容
        Map<String, Object> tokenResp = new HashMap<>();
        tokenResp.put("access_token", authorizedClient.getAccessToken().getTokenValue());
        tokenResp.put("token_type", authorizedClient.getAccessToken().getTokenType().getValue());
        tokenResp.put("expires_in", authorizedClient.getAccessToken().getExpiresAt().getEpochSecond() - Instant.now().getEpochSecond());
        if (authorizedClient.getRefreshToken() != null) {
            tokenResp.put("refresh_token", authorizedClient.getRefreshToken().getTokenValue());
        }

        // 返回JSON格式令牌
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getWriter(), tokenResp);
    }
}

网关安全配置(禁用Session)

@Configuration
public class GatewaySecurityConfig {

    private final StatelessTokenHandler tokenHandler;

    public GatewaySecurityConfig(StatelessTokenHandler tokenHandler) {
        this.tokenHandler = tokenHandler;
    }

    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
        http
                .csrf(ServerHttpSecurity.Csrf::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 强制无状态
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers("/login", "/oauth2/authorization/**", "/login/oauth2/code/**").permitAll()
                        .anyExchange().authenticated()
                )
                .oauth2Login(oauth2 -> oauth2
                        .authorizationRequestRepository(new CookieOAuth2AuthorizationRequestRepository()) // 用Cookie存储授权请求,替代Session
                        .authenticationSuccessHandler(tokenHandler) // 绑定自定义令牌返回处理器
                );
        return http.build();
    }
}

4. Spring Authorization Server基础配置(自行搭建时)

确保授权服务器配置的客户端信息与网关一致,支持授权码流程:

@Configuration
@EnableAuthorizationServer
public class AuthServerConfig {

    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("your-client-id")
                .clientSecret("{noop}your-client-secret") // 生产环境需使用加密密码
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://gateway:8080/login/oauth2/code/spring") // 与网关回调地址一致
                .scope("openid")
                .scope("profile")
                .build();
        return new InMemoryRegisteredClientRepository(client);
    }

    // 可补充用户认证、JWT令牌生成等其他基础配置
}

流程验证

  1. 访问网关/login端点,自动重定向到/oauth2/authorization/spring,随后跳转至授权服务器的登录页面
  2. 输入合法用户凭证后,授权服务器回调网关的/login/oauth2/code/spring端点
  3. 网关通过自定义处理器完成授权码到令牌的交换,直接返回JSON格式的令牌给客户端,全程无会话创建,保持无状态特性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:01:29