首页 / 帮助文档 / 网站开发框架SpringCloudGateway的安全过滤器排序

网站开发框架SpringCloudGateway的安全过滤器排序

在Spring Cloud Gateway中配置安全过滤器时,很多人会直接照搬文档上的示例,把一堆过滤器往配置文件里一塞,结果发现认证、限流、跨域等功能莫名其妙地失效或行为异常。这背后十有八九是过滤器的执行顺序出了问题。Spring Cloud Gateway的过滤器体系分为全局过滤器(GlobalFilter)和路由级过滤器(GatewayFilter),它们各自有默认的排序机制,而当两者混用时,顺序问题就变得尤为隐蔽和棘手。

过滤器排序的核心机制

Spring Cloud Gateway为所有过滤器定义了Ordered接口,排序数值越小,执行优先级越高。全局过滤器通过实现GlobalFilter接口并重写getOrder()方法来声明顺序,而路由级过滤器则是在路由定义时通过元数据指定顺序。关键点在于,框架内部把全局过滤器和路由级过滤器放在同一条执行链上,统一按照Order值排序,这就意味着如果你自定义的全局过滤器Order值设置不当,它可能会插在路由级过滤器的中间,甚至跑到它们前面去。

框架内置的全局过滤器都有明确的默认Order值。NettyRoutingFilter的Order是Integer.MAX_VALUE,负责最后发起请求;NettyWriteResponseFilter的Order是-1,负责写回响应;而安全相关的过滤器,比如ReactiveAuthorizationFilter,通常处于较靠前的位置。具体数值可以在官方文档的附录中查到,但更重要的是理解这些数值背后的设计意图:认证类过滤器要尽早执行,日志和监控类过滤器可以稍晚,而真正发起代理请求的过滤器必须放在最后。

自定义安全过滤器的排序陷阱

假设你写了一个JWT鉴权的全局过滤器,用来校验请求头中的Token。如果你不显式指定Order值,Spring会默认给一个Ordered.LOWEST_PRECEDENCE,也就是Integer.MAX_VALUE,这意味着你的鉴权过滤器会排在整个过滤器链的最末尾,甚至排在NettyRoutingFilter之后。结果就是请求已经被转发到下游服务并返回了响应,你的鉴权逻辑才姗姗来迟,完全起不到拦截作用。

正确的做法是让安全过滤器尽早执行。通常建议将认证授权类过滤器的Order值设置在-100到0之间,具体要看你的过滤器需要依赖哪些前置处理。例如,如果你的鉴权逻辑需要读取请求体,而请求体可能被BodyExtractor之类的过滤器缓存过,那你的Order值就要大于缓存过滤器的Order值,确保缓存已经完成。下面是一个典型的自定义全局过滤器示例:

@Component
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 从请求头中提取并校验JWT
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !validateToken(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 确保在大多数内置过滤器之前执行
        return -100;
    }
}

这里将Order设为-100,是为了排在大部分内置全局过滤器之前,但又不会过于靠前以至于影响框架的基础初始化。实际上,Spring Security集成的ReactiveAuthorizationFilter默认Order值就在这个区间附近,保持相近的数值可以让你的自定义逻辑与框架安全逻辑协调工作。

路由级过滤器的排序规则

路由级过滤器的排序更灵活,但也更容易出错。在YAML配置中,过滤器的书写顺序并不等于执行顺序。Spring Cloud Gateway会根据每个过滤器在路由定义中的“先后位置”生成一个默认的Order值,但这个默认值是从1开始递增的,并且只在该路由的过滤器链内部有效。当路由级过滤器与全局过滤器混合排序时,框架会把路由级过滤器的Order值加上一个偏移量,然后与全局过滤器一起排序。

这个偏移量机制是很多人踩坑的地方。默认情况下,路由级过滤器的Order值会从1开始计数,但全局过滤器的Order值可能是负数,这就导致路由级过滤器天然排在全局过滤器之后。如果你有一个Order为-50的全局安全过滤器,而你的路由级限流过滤器默认Order是1,那么安全过滤器会先执行,限流过滤器后执行,这通常是合理的。但如果你把限流逻辑写成了全局过滤器且Order值设得比安全过滤器还小,限流就会在鉴权之前触发,未认证的请求也会消耗限流计数,这显然不是你想要的效果。

要精确控制路由级过滤器的顺序,可以在配置中显式指定Order值。例如,使用StripPrefix过滤器时,你可以通过参数控制它的执行顺序:

spring:
  cloud:
    gateway:
      routes:
        - id: secure_route
          uri: http://backend-service
          filters:
            - name: StripPrefix
              args:
                parts: 1
                order: 50
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20
                order: 100

这里通过args中的order参数显式指定了StripPrefix和RequestRateLimiter的顺序。需要注意的是,并非所有内置过滤器都支持在args中直接设置order,具体要查看对应过滤器工厂的实现是否读取了这个参数。对于自定义的路由级过滤器,你可以在GatewayFilterFactory的实现中通过Ordered接口来设定默认顺序,并在apply方法中返回一个带Order的GatewayFilter实例。

多个安全过滤器协同的场景

在实际项目中,安全往往不是单一维度的事情。你可能同时需要IP白名单校验、JWT鉴权、RBAC权限控制、请求签名验证等多个安全过滤器。这些过滤器之间的顺序直接影响安全策略的有效性。基本原则是:越基础的校验越靠前,越细粒度的控制越靠后。IP白名单应该最先执行,因为它能最快地拒绝掉明显非法的请求,减少后续开销。接下来是请求签名验证,确保请求未被篡改。然后是JWT鉴权,确认用户身份。最后才是RBAC权限控制,判断用户是否有权访问特定资源。

把这些过滤器按Order值从小到大排列,比如IP白名单设为-200,签名验证设为-150,JWT鉴权设为-100,RBAC设为-50。每个过滤器在发现不满足条件时直接返回错误响应,不再调用chain.filter(),这样后续过滤器就不会被执行,形成一条清晰的短路链路。下面是一个简化的配置思路,实际代码中这些过滤器可以各自独立实现:

// IP白名单过滤器 Order = -200
// 请求签名验证过滤器 Order = -150
// JWT鉴权过滤器 Order = -100
// RBAC权限过滤器 Order = -50

这种分层设计还有一个好处:当某个安全策略需要调整或替换时,只需要修改对应过滤器的Order值或实现,不会影响其他安全模块。同时,明确的数值间隔也预留了插入新过滤器的空间,比如后来需要在签名验证和JWT鉴权之间加一个设备指纹校验,直接设为-125即可。

与Spring Security集成的排序细节

如果你在Spring Cloud Gateway中集成了Spring Security,情况会稍微复杂一些。Spring Security本身通过ReactiveSecurityContextHolder来传递认证信息,而Spring Cloud Gateway的过滤器链与Spring Security的WebFilter链是两条不同的链路,但它们在同一个请求处理流程中按顺序执行。Spring Security的WebFilter通常会在Gateway的过滤器链之前执行,这是因为Spring Security的自动配置将它的Order设得比较低。

具体来说,Spring Security的ReactiveWebFilterChainProxy默认Order是-100,而Gateway的RoutePredicateHandlerMapping处理的过滤器链Order是0。这意味着Spring Security的过滤器会先于Gateway的大部分路由级过滤器执行。如果你的自定义安全过滤器需要依赖Spring Security的认证结果,那就要确保它的Order值大于-100,也就是排在Spring Security过滤器之后。反之,如果你希望自定义过滤器在Spring Security之前做一些预处理,比如把某种私有协议头转换为标准的Authorization头,那Order值就要小于-100。

一个常见的错误是,在Gateway的全局过滤器中直接使用ReactiveSecurityContextHolder获取认证信息,但此时Spring Security的过滤器还未执行,Context里自然是空的。解决方法是调整Order值,让自定义过滤器排在Spring Security之后。例如:

@Component
public class CustomAuthorizationFilter implements GlobalFilter, Ordered {

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return ReactiveSecurityContextHolder.getContext()
            .map(SecurityContext::getAuthentication)
            .flatMap(auth -> {
                // 基于auth做权限判断,auth此时不为null
                return chain.filter(exchange);
            })
            .switchIfEmpty(Mono.defer(() -> {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }));
    }

    @Override
    public int getOrder() {
        // 必须大于Spring Security的-100,建议设为-50
        return -50;
    }
}

这个过滤器在Spring Security完成认证之后执行,因此能正确获取到Authentication对象。如果你把Order设为-150,就会在Spring Security之前执行,那时SecurityContext还是空的,导致即使携带了有效Token也会被拒绝。

调试和验证过滤器顺序

排查过滤器顺序问题最直接的方法是开启Gateway的调试日志。在application.yml中设置logging.level.org.springframework.cloud.gateway为DEBUG,启动后可以在日志中看到过滤器链的完整顺序。每个过滤器在执行前后都会打印日志,通过观察日志输出的先后,能直观地确认实际执行顺序是否符合预期。

另一种方法是在自定义过滤器中打印Order值。你可以在filter方法的第一行用log输出当前过滤器的类名和Order值,这样在请求处理过程中,通过日志顺序就能还原出完整的执行链。对于复杂项目,建议写一个简单的Controller来专门测试过滤器顺序,用不同路径触发不同路由,观察各过滤器的执行情况。

如果发现顺序不对,优先检查全局过滤器的getOrder()返回值,以及路由配置中是否通过args指定了order。注意,有些内置过滤器工厂的默认Order是硬编码的,比如AddRequestHeader过滤器工厂默认Order是1,如果你需要它在某个自定义过滤器之后执行,要么调整自定义过滤器的Order大于1,要么在配置中显式指定AddRequestHeader的order值。

生产环境的排序最佳实践

在生产环境中,建议将所有安全相关过滤器的Order值集中定义在一个常量类或配置文件中,避免散落在各个类里导致维护困难。可以定义一个SecurityFilterOrderConstants类,里面用清晰的常量名来表达每个过滤器的职责和顺序:

public final class SecurityFilterOrderConstants {
    public static final int IP_WHITELIST = -200;
    public static final int SIGNATURE_VERIFICATION = -150;
    public static final int JWT_AUTHENTICATION = -100;
    public static final int RBAC_AUTHORIZATION = -50;
    public static final int RATE_LIMITER = 0;
}

每个自定义过滤器实现Ordered时直接引用这些常量,这样当需要调整整体安全策略的顺序时,只需修改常量值,所有相关过滤器自动跟随变化。同时,在代码评审时,审查者也能一目了然地看到整个安全体系的层次结构。

另外,要注意过滤器中的阻塞操作。Spring Cloud Gateway基于Reactor,过滤器链是异步非阻塞的,如果在过滤器中执行了阻塞调用(如同步HTTP调用、数据库查询等),会严重影响吞吐量。安全过滤器中常见的JWT解析、Redis查询等操作应该使用响应式库,比如用ReactiveRedisTemplate代替Jedis,用WebClient代替RestTemplate。如果必须使用阻塞库,可以通过Schedulers.boundedElastic()将阻塞操作调度到弹性线程池,但这只是权宜之计,尽量从根源上避免阻塞。

最后,定期审查过滤器链的健康状况。随着业务迭代,可能会不断添加新的过滤器,原本合理的顺序可能逐渐变得混乱。建议在项目文档中维护一份过滤器执行顺序图,并在每次新增安全过滤器时更新。结合自动化测试,用单元测试验证关键路径上过滤器的执行顺序和短路行为,确保安全策略始终按设计运行。