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

Spring Cloud Gateway+Security JWT注册登录403错误求助

微服务认证问题排查与解决方案

当前环境与配置

API网关(WebFlux)Security配置

网关基于Spring Cloud Gateway(Reactive栈),配置如下:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity){

        serverHttpSecurity
                .authorizeExchange(exchange -> exchange
                        .pathMatchers(
                                "/api/**",
                                "/eureka/**"
                        )
                        .permitAll()
                        .anyExchange()
                        .authenticated())
                .csrf()
                .disable()
                .oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt);
        return serverHttpSecurity.build();
    }
}

Identity Service(Servlet栈)Security配置

该服务负责用户注册登录,采用自定义JWT认证方案,配置如下:

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    private final JwtFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{
        httpSecurity
                .cors(withDefaults())
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeRequests(request -> request.regexMatchers(
                        "/api/auth/*",
                        "/v2/api-docs",
                        "/v3/api-docs",
                        "/v3/api-docs/*",
                        "/swagger-resources/",
                        "/swagger-resources/*",
                        "/configuration/ui",
                        "/configuration/security",
                        "/swagger-ui/",
                        "/webjars/*",
                        "/swagger-ui.html"
                        )
                        .permitAll()
                        .anyRequest()
                        .authenticated()
                )
                .sessionManagement(
                        session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }
}

遇到的问题

通过Postman向http://localhost:8080/api/auth/register发送携带自定义JWT的POST请求时,返回403 Forbidden。网关日志显示请求已路由到identity-service,但最终返回403:

2024-04-12 20:15:02.924 TRACE [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/eureka/web" does not match against value "/api/auth/register"
2024-04-12 20:15:02.924 TRACE [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/api/auth/**" matches against value "/api/auth/register"
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: identity-service
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: POST http://localhost:8080/api/auth/register] to Route{id='identity-service', uri=lb://identity-service, order=0, predicate=Paths: [/api/auth/**], match trailing slash: true, gatewayFilters=[[[SpringCloudCircuitBreakerResilience4JFilterFactory name = 'resilience', fallback = /identity-fallback], order = 0]], metadata={}}
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.h.RoutePredicateHandlerMapping   : [ade0fee6-4] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@56568494
2024-04-12 20:15:02.924 DEBUG [xpense-gateway,,] 35685 --- [     parallel-8] o.s.c.g.handler.FilteringWebHandler      : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@7a3a49e5}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@305881b8}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@6dbb3d7d}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@3ea9a091}, order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.GatewayMetricsFilter@26495639}, order = 0], [[SpringCloudCircuitBreakerResilience4JFilterFactory name = 'resilience', fallback = /identity-fallback], order = 0], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@6c1b82cd}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter@54687fd0}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter@6eaf030c}, order = 10151], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@16f4a3c0}, order = 2147483646], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@b2da3a5}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@acd3460}, order = 2147483647]]
2024-04-12 20:15:02.924 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] o.s.c.g.filter.RouteToRequestUrlFilter   : RouteToRequestUrlFilter start
2024-04-12 20:15:02.924 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] s.c.g.f.ReactiveLoadBalancerClientFilter : ReactiveLoadBalancerClientFilter url before: lb://identity-service/api/auth/register
2024-04-12 20:15:02.925 TRACE [xpense-gateway,ffa6cb10b03275d4,5984f380554abaa0] 35685 --- [     parallel-8] s.c.g.f.ReactiveLoadBalancerClientFilter : LoadBalancerClientFilter url chosen: http://VFIEVOX3.Router:35185/api/auth/register
2024-04-12 20:15:02.945 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.gateway.filter.NettyRoutingFilter  : outbound route: dc99dbca, inbound: [ade0fee6-4] 
2024-04-12 20:15:02.951 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.g.filter.NettyWriteResponseFilter  : NettyWriteResponseFilter start inbound: dc99dbca, outbound: [ade0fee6-4] 
2024-04-12 20:15:02.952 TRACE [xpense-gateway,,] 35685 --- [r-http-epoll-11] o.s.c.g.filter.GatewayMetricsFilter      : spring.cloud.gateway.requests tags: [tag(httpMethod=POST),tag(httpStatusCode=403),tag(outcome=CLIENT_ERROR),tag(routeId=identity-service),tag(routeUri=lb://identity-service),tag(status=FORBIDDEN)]

核心疑问

  1. SecurityFilterChain与SecurityWebFilterChain的具体区别
  2. 如何在网关的SecurityWebFilterChain中实现无状态会话管理
  3. 如何脱离Keycloak,搭建自定义的全链路认证授权机制

解决方案

1. 403错误根源排查

从网关日志可知请求已成功路由到identity-service,403的核心原因是网关的JWT验证不通过:

  • 网关配置了.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt),该配置会让网关使用Keycloak的JWT验证规则(签名算法、issuer、audience等)校验请求中的Token
  • 而你使用的是自定义生成的JWT,与Keycloak的Token格式/签名规则不匹配,导致网关直接拒绝请求

2. SecurityFilterChain vs SecurityWebFilterChain

类型适用场景核心API编程模型
SecurityFilterChain传统Servlet栈Spring Boot应用HttpSecurity同步阻塞
SecurityWebFilterChainReactive栈应用(如Gateway)ServerHttpSecurity异步非阻塞

两者分别对应Spring MVC和Spring WebFlux技术栈,配置API与底层处理逻辑完全独立。

3. 网关实现无状态会话管理

WebFlux Security默认就是无状态的,Reactive栈本身不依赖Servlet会话。若要强化无状态特性,可添加以下配置:

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity) {
    serverHttpSecurity
            // ... 其他配置
            .securityContextRepository(NoOpServerSecurityContextRepository.getInstance()); // 禁用安全上下文存储,彻底无状态
    return serverHttpSecurity.build();
}

无需额外配置会话管理,只要不启用依赖会话的认证方式(如表单登录)即可。

4. 自定义全链路认证改造

网关侧改造(替换Keycloak资源服务器为自定义JWT验证)

移除Keycloak相关配置,实现自定义Reactive JWT认证:

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity serverHttpSecurity, 
                                                        ReactiveAuthenticationManager jwtAuthManager) {
        serverHttpSecurity
                .authorizeExchange(exchange -> exchange
                        .pathMatchers("/api/auth/**", "/eureka/**")
                        .permitAll()
                        .anyExchange()
                        .authenticated())
                .csrf().disable()
                .authenticationManager(jwtAuthManager)
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance());

        return serverHttpSecurity.build();
    }

    // 自定义Reactive JWT认证管理器
    @Bean
    public ReactiveAuthenticationManager jwtReactiveAuthenticationManager(JwtDecoder jwtDecoder) {
        JwtReactiveAuthenticationManager manager = new JwtReactiveAuthenticationManager(jwtDecoder);
        // 可扩展自定义JWT转换逻辑,将JWT Claims转换为Authentication对象
        manager.setJwtAuthenticationConverter(jwt -> {
            return Mono.just(new UsernamePasswordAuthenticationToken(
                    jwt.getSubject(),
                    null,
                    Collections.emptyList()
            ));
        });
        return manager;
    }

    // 自定义JWT解码器,需与identity-service的Token生成规则一致
    @Bean
    public JwtDecoder jwtDecoder() {
        // 示例:使用对称密钥验证签名,密钥需与identity-service生成Token时一致
        SecretKey secretKey = Keys.hmacShaKeyFor("your-strong-custom-secret-key-1234567890".getBytes(StandardCharsets.UTF_8));
        return NimbusJwtDecoder.withSecretKey(secretKey).build();
    }
}

Identity Service侧优化

  • 修正路径匹配规则:将regexMatchers("/api/auth/*")改为antMatchers("/api/auth/**"),避免正则匹配的局限性
  • 确保JwtFilter正确解析Token,AuthenticationProvider能正确验证用户身份信息

5. 快速验证步骤

  1. 直接请求identity-service的端口(如http://localhost:35185/api/auth/register),确认服务本身是否正常
  2. 检查网关与identity-service的JWT密钥、签名算法是否完全一致
  3. 开启Spring Security DEBUG日志,查看具体认证失败细节:
    logging.level.org.springframework.security=DEBUG
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:15:54