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

Spring Cloud Gateway集成OAuth认证路由404问题求助

Spring Cloud Gateway集成OAuth时404及Security冲突问题解决

问题核心分析

  1. 404错误原因:网关路由规则中,ms_oauth服务的匹配路径是/oauth2/**,但实际请求路径是/ms_oauth/oauth2/token,路径前缀/ms_oauth导致路由匹配失败,网关找不到对应的路由规则转发请求。
  2. Security配置冲突:网关是Reactive环境(配置了spring.main.web-application-type=reactive),但同时启用了@EnableWebSecurity(Servlet环境安全配置)和@EnableWebFluxSecurity(Reactive环境安全配置),两者混用导致CSRF等配置冲突。

解决方案

1. 调整网关路由配置,修复404问题

修改网关的application.properties,调整ms_oauth服务的路由规则,使其匹配带/ms_oauth前缀的路径,并通过过滤器移除前缀后转发到OAuth服务:

spring.cloud.gateway.routes[1].id=ms_oauth
spring.cloud.gateway.routes[1].uri=lb://ms_oauth
# 匹配带/ms_oauth前缀的oauth2路径
spring.cloud.gateway.routes[1].predicates=Path=/ms_oauth/oauth2/**
# 添加RewritePath过滤器,移除/ms_oauth前缀,转发到服务的/oauth2/**路径
spring.cloud.gateway.routes[1].filters[0].name=RewritePath
spring.cloud.gateway.routes[1].filters[0].args.regexp=/ms_oauth/(?<segment>.*)
spring.cloud.gateway.routes[1].filters[0].args.replacement=/${segment}
# 保留原有的RewriteResponseHeader过滤器
spring.cloud.gateway.routes[1].filters[1].name=RewriteResponseHeader
spring.cloud.gateway.routes[1].filters[1].args.name=Set-Cookie
spring.cloud.gateway.routes[1].filters[1].args.regexp=.*
spring.cloud.gateway.routes[1].filters[1].args.replacement=secure-cookie=value; Secure

说明:通过RewritePath过滤器将请求路径/ms_oauth/oauth2/token转换为/oauth2/token,转发到ms_oauth服务,这样就能正确匹配服务端的接口路径。

2. 修复网关Security配置冲突

网关是Reactive环境,需移除Servlet环境的安全配置,只保留Reactive相关配置:

@Configuration
@EnableWebFluxSecurity
@EnableMethodSecurity
public class ResourceServerConfig {

    @Value("${cors.origins}")
    private String corsOrigins;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .authorizeExchange(exchanges -> exchanges
                        // 允许oauth2相关请求无需认证
                        .pathMatchers("/ms_oauth/oauth2/**").permitAll()
                        .anyExchange().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
                .cors(cors -> cors.configurationSource(corsConfigurationSource()));
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        grantedAuthoritiesConverter.setAuthorityPrefix("");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        RSAKey rsaKey = generateRsa();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }

    private static RSAKey generateRsa() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
    }

    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(2048);
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        String[] origins = corsOrigins.split(",");

        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
        corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH"));
        corsConfig.setAllowCredentials(true);
        corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig);
        return source;
    }
}

说明:

  • 移除@EnableWebSecurity和所有Servlet环境的SecurityFilterChain、FilterRegistrationBean<CorsFilter>,避免与Reactive环境冲突。
  • 在SecurityWebFilterChain中配置路径规则,允许/ms_oauth/oauth2/**路径无需认证(获取token的请求本身不需要认证),其他路径需认证。
  • 保留Reactive环境的CORS配置,由ServerHttpSecurity统一处理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 07:24:55