Spring Cloud Gateway MVC自定义过滤器执行顺序不可控问题咨询
解决方案
方案1:调整日志记录时机到请求处理后
AbstractRequestLoggingFilter默认会在请求处理完成后(filterChain.doFilter执行后)调用afterRequest方法,此时Gateway已经完成授权服务调用并添加了请求头,你可以在这里记录包含目标请求头的日志:
修改RequestResponseFilter,重写afterRequest方法来记录所需日志:
@Slf4j public class RequestResponseFilter extends AbstractRequestLoggingFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { super.doFilterInternal(request, response, filterChain); } finally { MDC.clear(); } } @Override protected void afterRequest(HttpServletRequest request, String message) { // 获取授权服务添加的请求头 String authHeader = request.getHeader("X-Auth-Header"); // 记录包含授权头的日志 log.info("请求处理完成,授权头: {}, 日志信息: {}", authHeader, message); // 调用父类方法保留原有日志逻辑 super.afterRequest(request, message); } }
这样无需调整过滤器顺序,就能确保日志中包含授权服务添加的请求头。
方案2:将授权逻辑移至Servlet Filter并调整执行顺序
如果必须在请求处理前记录包含授权头的日志,需要把授权服务调用逻辑封装为一个Servlet Filter,并且让它的执行顺序早于RequestResponseFilter:
- 实现授权Filter:
@Slf4j public class AuthEnrichFilter implements Filter { private final AuthService authService; // 注入授权服务客户端 public AuthEnrichFilter(AuthService authService) { this.authService = authService; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; // 调用授权服务获取数据 AuthInfo authInfo = authService.getAuthInfo(httpRequest.getHeader("Authorization")); // 包装请求,添加自定义请求头 HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public String getHeader(String name) { if ("X-Auth-Header".equals(name)) { return authInfo.getUserId(); // 示例:添加用户ID作为请求头 } return super.getHeader(name); } @Override public Enumeration<String> getHeaders(String name) { if ("X-Auth-Header".equals(name)) { return Collections.enumeration(Collections.singletonList(authInfo.getUserId())); } return super.getHeaders(name); } @Override public Enumeration<String> getHeaderNames() { Set<String> headerNames = Collections.list(super.getHeaderNames()); headerNames.add("X-Auth-Header"); return Collections.enumeration(headerNames); } }; // 继续执行Filter链 chain.doFilter(wrappedRequest, response); } }
- 注册Filter并指定顺序:
在Gateway项目中配置Filter的注册Bean,确保授权Filter的order小于RequestResponseFilter(数值越小,执行越早):
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<AuthEnrichFilter> authEnrichFilterRegistration(AuthService authService) { FilterRegistrationBean<AuthEnrichFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new AuthEnrichFilter(authService)); registration.addUrlPatterns("/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); // 优先级高于默认Filter return registration; } // 若logging-starter中已注册RequestResponseFilter,可调整其order确保在授权Filter之后 @Bean public FilterRegistrationBean<RequestResponseFilter> requestResponseFilterRegistration() { FilterRegistrationBean<RequestResponseFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new RequestResponseFilter()); registration.addUrlPatterns("/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 2); // 晚于授权Filter执行 return registration; } }
这样授权Filter会先执行,添加请求头后,RequestResponseFilter再执行日志记录,就能拿到所需头信息。
方案3:使用Gateway MVC的Global Filter控制顺序
如果希望保留Gateway层面的授权逻辑,可将日志逻辑也迁移至Gateway的Global Filter,并通过Ordered接口明确控制执行顺序:
- 实现授权Global Filter:
@Component public class AuthEnrichGatewayFilter implements GatewayFilter, Ordered { private final AuthService authService; public AuthEnrichGatewayFilter(AuthService authService) { this.authService = authService; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 从请求中获取授权凭证 String authToken = exchange.getRequest().getHeaders().getFirst("Authorization"); // 调用授权服务 return authService.getAuthInfo(authToken) .map(authInfo -> { // 构建新的请求头,添加授权信息 HttpHeaders newHeaders = new HttpHeaders(); newHeaders.putAll(exchange.getRequest().getHeaders()); newHeaders.add("X-Auth-Header", authInfo.getUserId()); // 包装请求 ServerHttpRequest newRequest = exchange.getRequest().mutate() .headers(httpHeaders -> httpHeaders.putAll(newHeaders)) .build(); // 更新exchange return exchange.mutate().request(newRequest).build(); }) .flatMap(chain::filter); } @Override public int getOrder() { return 1; // 优先级高,先执行 } }
- 实现日志Global Filter:
@Component public class RequestLoggingGatewayFilter implements GatewayFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(RequestLoggingGatewayFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 记录请求头(此时已添加授权头) String authHeader = exchange.getRequest().getHeaders().getFirst("X-Auth-Header"); log.info("请求日志,授权头: {}, 请求路径: {}", authHeader, exchange.getRequest().getPath()); // 继续执行链 return chain.filter(exchange); } @Override public int getOrder() { return 2; // 晚于授权Filter执行 } }
- 排除logging-starter中的Servlet Filter:
在Gateway项目的application.yml中排除原有的RequestResponseFilter注册,避免重复日志:
spring: autoconfigure: exclude: com.yourpackage.logging.RequestResponseFilterAutoConfiguration # 替换为logging-starter中注册Filter的配置类
这种方式完全基于Gateway MVC的处理流程,通过明确的order值确保授权逻辑先执行,日志逻辑后执行,避免顺序不可控的问题。
内容的提问来源于stack exchange,提问作者Niyaz Khannanov
相关产品推荐
相关产品推荐

