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

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:

  1. 实现授权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);
    }
}
  1. 注册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接口明确控制执行顺序:

  1. 实现授权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; // 优先级高,先执行
    }
}
  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执行
    }
}
  1. 排除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:24:53