Spring Cloud API Gateway认证通过仍返回401问题求助
问题描述
基于Spring Cloud构建API Gateway,网关和user service均实现JWT认证机制。当前问题:访问需认证的接口时,网关返回401,但日志显示JWT验证已成功;将这些接口加入免认证路由后可正常访问。已将下游服务从servlet改为reactive(依赖WebFlux)以避免兼容性问题,单独调用服务时功能正常。
相关代码片段
GatewaySecurityConfig
package com.OrderIt.api_gateway.auth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.web.server.SecurityWebFilterChain; @Configuration public class GatewaySecurityConfig { @Autowired private JwtAuthFilter jwtAuthFilter; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http .csrf(ServerHttpSecurity.CsrfSpec::disable) // Updated syntax for disabling CSRF .authorizeExchange(exchanges -> exchanges .pathMatchers(HttpMethod.POST, "/api/user").permitAll() // Allow POST requests to /api/user // .pathMatchers(HttpMethod.GET, "/api/user").permitAll() .pathMatchers("/api/auth/**").permitAll() .pathMatchers("/api/item/**").authenticated()// Allow unauthenticated access to /authenticate // .anyExchange().authenticated() // Require authentication for all other endpoints ).addFilterBefore(jwtAuthFilter, SecurityWebFiltersOrder.AUTHENTICATION) .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) // Disable HTTP Basic authentication .formLogin(ServerHttpSecurity.FormLoginSpec::disable); return http.build(); } }
JwtAuthFilter
package com.OrderIt.api_gateway.auth; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import java.util.List; @Component public class JwtAuthFilter implements WebFilter { private static final Logger log = LoggerFactory.getLogger(JwtAuthFilter.class); @Autowired private JwtUtil jwtUtil; private final AntPathMatcher pathMatcher = new AntPathMatcher(); // Define the list of excluded routes with HTTP methods private final List<RouteConfig> excludedRoutes = List.of( new RouteConfig("/api/user", HttpMethod.POST), // Exclude POST requests to /api/user new RouteConfig("/api/auth/**", null) // Exclude all requests to /api/auth/** // new RouteConfig("/api/item/**", null) ); @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { log.debug("JwtAuthFilter is processing the request"); String path = exchange.getRequest().getURI().getPath(); HttpMethod method = exchange.getRequest().getMethod(); log.debug("Incoming request path: {}, method: {}", path, method); // Skip JWT validation for excluded routes if (shouldExclude(path, method)) { log.debug("Skipping JWT validation for excluded route: {}, method: {}", path, method); return chain.filter(exchange); // Continue without validating JWT } // JWT validation starts here String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (authHeader == null || !authHeader.startsWith("Bearer ")) { log.warn("Missing or invalid Authorization header"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } String token = authHeader.substring(7); // Extract the token after "Bearer " if (!jwtUtil.validateToken(token)) { log.warn("Invalid JWT token"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } log.debug("JWT token validated successfully"); // Forward the token to downstream services ServerHttpRequest request = exchange.getRequest() .mutate() .header(HttpHeaders.AUTHORIZATION, authHeader) // Forward the auth header .build(); return chain.filter(exchange.mutate().request(request).build()) .doOnSuccess(aVoid -> { log.debug("Successfully completed processing for path: {}", path); }) .doOnError(throwable -> { log.error("Error occurred while processing for path: {}, error: {}", path, throwable.getMessage()); }) .then(Mono.fromRunnable(() -> { log.debug("Response status code: {}", exchange.getResponse().getStatusCode()); })); } private boolean shouldExclude(String path, HttpMethod method) { // Check if the path and method match any exclusion in the list return excludedRoutes.stream().anyMatch(routeConfig -> pathMatcher.match(routeConfig.getPath(), path) && (routeConfig.getMethod() == null || routeConfig.getMethod().equals(method)) ); } // Helper class to define route and method exclusions private static class RouteConfig { private final String path; private final HttpMethod method; public RouteConfig(String path, HttpMethod method) { this.path = path; this.method = method; } public String getPath() { return path; } public HttpMethod getMethod() { return method; } } }
日志信息
ttp-nio-3] [ ] c.O.api_gateway.auth.JwtAuthFilter : JwtAuthFilter is processing the request 2024-10-29T01:21:22.102+05:30 DEBUG 32596 --- [api-gateway] [ctor-http-nio-3] [ ] c.O.api_gateway.auth.JwtAuthFilter : Incoming request path: /api/item, method: POST 2024-10-29T01:21:22.658+05:30 DEBUG 32596 --- [api-gateway] [ctor-http-nio-3] [ ] c.O.api_gateway.auth.JwtAuthFilter : JWT token validated successfully 2024-10-29T01:21:22.675+05:30 DEBUG 32596 --- [api-gateway] [ parallel-4] [ ] c.O.api_gateway.auth.JwtAuthFilter : Successfully completed processing for path: /api/item 2024-10-29T01:21:22.675+05:30 DEBUG 32596 --- [api-gateway] [ parallel-4] [ ] c.O.api_gateway.auth.JwtAuthFilter : Response status code: 401 UNAUTHORIZED
原因分析
- Spring Security上下文未更新:JwtAuthFilter仅完成JWT验证,但未将认证信息写入Spring Security的
SecurityContext。网关的authorizeExchange规则要求/api/item/**必须认证,由于SecurityContext中无用户认证信息,Spring Security仍判定为未认证并返回401。 - 过滤器逻辑缺失:自定义过滤器仅验证了JWT有效性,但未完成Spring Security认证流程的关键步骤——设置认证上下文,导致后续安全拦截器无法识别已通过验证的用户。
排查建议
1. 在JwtAuthFilter中补充SecurityContext设置
验证JWT成功后,构建Authentication对象并写入SecurityContext:
// 注入ServerSecurityContextRepository @Autowired private ServerSecurityContextRepository securityContextRepository; // JWT验证成功后添加以下代码 String username = jwtUtil.extractUsername(token); Authentication authentication = new UsernamePasswordAuthenticationToken( username, null, Collections.emptyList() // 若有权限信息,替换为实际权限列表 ); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(authentication); // 保存上下文并继续执行过滤器链 return securityContextRepository.save(exchange, securityContext) .then(chain.filter(exchange.mutate().request(request).build()));
2. 调整SecurityWebFilterChain配置
- 暂时将
.pathMatchers("/api/item/**").authenticated()替换为anyExchange().authenticated(),测试是否仍出现401,排查规则配置是否存在冲突。 - 确认过滤器顺序:
addFilterBefore(jwtAuthFilter, SecurityWebFiltersOrder.AUTHENTICATION)是否确保JWT验证在Spring Security认证拦截器之前执行。
3. 验证下游服务的认证转发
在下游服务中添加日志,确认Authorization头是否被网关正确转发。若下游服务仍在执行JWT验证,需确保网关转发的Token与服务端验证逻辑兼容。
4. 开启Spring Security调试日志
添加日志配置,查看认证流程细节,确认SecurityContext是否被正确设置:
logging.level.org.springframework.security=DEBUG
内容的提问来源于stack exchange,提问作者Arunraj V R
相关产品推荐
相关产品推荐

