Skip to content

Spring-Cloud-Gateway实现自定义过滤器

前言

最近在做公司业务时需要在网关微服务自定义过滤器,特此记录总结下。

参考地址:https://blog.csdn.net/Cavewang/article/details/123854065 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。涉及转载内容目的是为了学习,无商业用途。

过滤器的执行次序

Spring-Cloud-Gateway 基于过滤器实现,同 zuul 类似,有pre和post两种方式的 filter,分别处理前置逻辑和后置逻辑。客户端的请求先经过pre类型的 filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过post类型的 filter 处理,最后返回响应到客户端。 过滤器执行流程如下,order 越大,优先级越低,如图所示。

过滤器分为全局过滤器和局部过滤器。

  • 全局过滤器:对所有路由生效

  • 局部过滤器:对指定的路由生效

全局过滤器

com.atguigu.gulimall.gateway.filters.FilterConfig

java
package com.atguigu.gulimall.gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class FilterConfig {

    @Bean
    public GlobalFilter a(){
        return new AFilter();
    }

    @Bean
    public GlobalFilter b(){
        return new BFilter();
    }

    @Bean
    public GlobalFilter c(){
        return new CFilter();
    }

    @Bean
    public GlobalFilter myAuthFilter(){
        return new MyAuthFilter();
    }

    @Slf4j
    static class AFilter implements GlobalFilter, Ordered{

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("AFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("AFilter后置逻辑");
            }));
        }

        @Override
        public int getOrder() {
            // 值越小,优先级越高
            return HIGHEST_PRECEDENCE + 100;
        }
    }

    @Slf4j
    static class BFilter implements GlobalFilter, Ordered{

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("BFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("BFilter后置逻辑");
            }));
        }

        @Override
        public int getOrder() {
            // 值越小,优先级越高
            return HIGHEST_PRECEDENCE + 200;
        }
    }

    @Slf4j
    static class CFilter implements GlobalFilter, Ordered{

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("CFilter前置逻辑");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("CFilter后置逻辑");
            }));
        }

        @Override
        public int getOrder() {
            // 值越小,优先级越高
            return HIGHEST_PRECEDENCE + 300;
        }
    }

    @Slf4j
    static class MyAuthFilter implements GlobalFilter,Ordered{

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            log.info("MyAuthFilter权限过滤器");
            String token = exchange.getRequest().getHeaders().getFirst("auth");
            if(StringUtils.isBlank(token)){
                // exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                // return exchange.getResponse().setComplete();
                log.info("无token,权限校验失败");
            }
            return chain.filter(exchange);
        }

        @Override
        public int getOrder() {
            return HIGHEST_PRECEDENCE + 400;
        }
    }
}

局部过滤器

  • 需要实现GatewayFilter, Ordered,实现相关的方法

  • 包装GatewayFilter,产生GatewayFilterFactory

  • GatewayFilterFactory加入到过滤器工厂,并且注册到spring容器中。

  • 在配置文件中进行配置,如果不配置则不启用此过滤器规则。

接下来定义局部过滤器,对于请求头user-id校验,如果不存在user-id请求头,直接返回状态码406。代码如下。

com.atguigu.gulimall.gateway.filters.UserIdCheckGatewayFilterFactory

java
package com.atguigu.gulimall.gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class UserIdCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new UserIdCheckGateWayFilter();
    }

    @Slf4j
    static class UserIdCheckGateWayFilter implements GatewayFilter, Ordered{

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String url = exchange.getRequest().getPath().pathWithinApplication().value();
            log.info("请求URL: {}",url);
            log.info("method: {}", exchange.getRequest().getMethod());
            // 获取header
            String userId = exchange.getRequest().getHeaders().getFirst("user-id");
            log.info("userId: {}",userId);

            if(StringUtils.isBlank(userId)){
                log.info("头部验证不通过,请在头部输入  user-id");
                //终止请求,直接回应
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }

        // 值越小,优先级越高
        @Override
        public int getOrder() {
            return HIGHEST_PRECEDENCE;
        }
    }
}

application.yml

yml
spring:
  cloud:
    gateway:
      routes:

        # 商品服务
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}

        # 第三方服务
        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

        # 后台管理服务
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

      default-filters:
        - PrefixPath=/api/product/**
        - UserIdCheck

这里可以发现我们在配置文件中直接使用UserIdCheck这个名字就可以自动识别到我们定义的局部过滤器,这是因为它继承自AbstractGatewayFilterFactory<Object>。Spring Cloud Gateway会根据你的GatewayFilterFactory的类名来生成一个过滤器引用的名字。具体来说,它会将类名中的GatewayFilterFactory后缀去掉,并将剩下的部分转换为小写(如果存在驼峰命名),然后用这个名字来引用过滤器。UserIdCheckGatewayFilterFactory的类名在去掉GatewayFilterFactory后缀后,变成了UserIdCheck,所以你可以在你的路由配置中直接使用UserIdCheck这个名字来引用这个过滤器。

可以看到自定义的局部过滤器已经成功生效了,但是我们之前定义的全局过滤器并未生效。

这是因为我们的局部过滤器的执行顺序高于全局过滤器,那么局部过滤器可能会先执行,并可能改变请求的状态或终止请求,导致全局过滤器无法执行或执行时看不到预期的效果。而我们局部过滤器中已经终止了本次请求,所有后续的全局过滤器并未执行。

上次更新于: