Gateway自定义过滤器——全局过滤器
作者:mmseoamin日期:2023-12-18

一、什么是全局过滤器🍉

首先,我们要知道全局过滤器其实是特殊路由过滤器(特殊的GatewayFilter),会有条件地作用于所有路由。

为什么要自定义全局过滤器?就好比是看大门的保安大叔,平时主要是做好进出大门外来人员登记即可,但是因为新冠疫情,现在还需要给外来人员测量体温等等。而已有的全局过滤器就好比是登记操作,而自定义的全局过滤器就好比是测量体温操作,是结合具体场景添加的。

比如以下场景:

  • 认证判断

  • 权限校验

  • 黑白名单

  • 跨域配置

    工作原理:

    当请求与路由匹配时,过滤WebHandler会将GlobalFilter的所有实例和GatewayFilter的所有特定于路由的实例添加到过滤器链中。该组合的过滤器链由org.springframework.core.Ordered接口排序,您可以通过实现getOrder()方法进行设置。

    由于Spring Cloud Gateway区分了过滤器逻辑执行的“前”阶段和“后”阶段,因此具有最高优先级的过滤器在“前”阶段中是第一个,在“后”阶段中是最后一个。

    二、自带的全局过滤器包含哪些🍉

    要自定义全局过滤,首先我们应该去了解一下gateway自带的主要的全局过滤器有哪些,并了解他们的作用。

    • ForwardRoutingFilter

      ForwardRoutingFilter在exchange属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR中查找URI。如果URL具有转发scheme(例如forward:/// localendpoint),则它将使用Spring DispatcherHandler来处理请求。请求URL的路径部分被转发URL中的路径覆盖。未经修改的原始URL会附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性中的列表中。

      • LoadBalancerClientFilter

        LoadBalancerClientFilter在ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的exchange属性中查找URI。如果URL的方案为lb(例如lb:// myservice),它将使用Spring Cloud LoadBalancerClient将名称(在本例中为myservice)解析为实际的主机和端口,并替换同一属性中的URI。未经修改的原始URL会附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR属性中的列表中。过滤器还会在ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性中查找其是否等于lb。如果是,则适用相同的规则。下面的清单配置一个LoadBalancerClientFilter:

        spring:
          cloud:
            gateway:
              routes:
              - id: myRoute
                uri: lb://service
                predicates:
                - Path=/service/**
        

        注意1:默认情况下,当在LoadBalancer中找不到服务实例时,将返回503。您可以通过设置spring.cloud.gateway.loadbalancer.use404 = true将网关配置为返回404。

        注意2:从LoadBalancer返回的ServiceInstance的isSecure值将覆盖对网关的请求中指定的方案。例如,如果请求通过HTTPS进入网关,但是ServiceInstance指示它不安全,则下游请求通过HTTP发出。相反的情况也可以适用。但是,如果在网关配置中为路由指定了GATEWAY_SCHEME_PREFIX_ATTR,则会删除前缀,并且路由URL产生的方案将覆盖ServiceInstance配置。

        注意3:LoadBalancerClientFilter在默认情况下使用阻塞的LoadBalancerClient。建议改用ReactiveLoadBalancerClientFilter。可以通过将spring.cloud.loadbalancer.ribbon.enabled的值设置为false来切换到该值。

        • ReactiveLoadBalancerClientFilter

          ReactiveLoadBalancerClientFilter与上面的LoadBalancerClientFilter类似,差异主要是ReactiveLoadBalancerClientFilter是非阻塞的。

          注意1:与LoadBalancerClientFilter一样,默认情况下,当ReactorLoadBalancer无法找到服务实例时,将返回503。您可以通过设置spring.cloud.gateway.loadbalancer.use404 = true将网关配置为返回404。

          • NettyRoutingFilter

            如果ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的exchange属性中的URL带有http或https,则将运行NettyRoutingFilter。它使用Netty HttpClient发出下游代理请求。响应将放入ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR的exchange属性中,供后面的过滤器使用。 (还有一个实验性的WebClientHttpRoutingFilter,它执行相同的功能,但不需要Netty。)

            经过该过滤器时,会通过ServerWebExchangeUtils.setAlreadyRouted方法把exchange对象标记为“已路由”。

            • RouteToRequestUrlFilter

              如果ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR的exchange属性中存在Route对象,则RouteToRequestUrlFilter将运行。它基于请求URI创建一个新URI,但使用Route对象的URI属性进行更新。新的URI放置在ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的exchange属性中。

              如果URI具有scheme前缀(例如lb:ws://serviceid),则将从URI中剥离lb方案,并将其放在ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR中,以供后面在过滤器链中使用。

              • WebsocketRoutingFilter

                顾名思义,WebsocketRoutingFilter过滤器是用于处理websocket请求的,如果ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR的exchange属性中的URL具有ws或wss的schema,则将运行WebsocketRoutingFilter。它使用Spring WebSocket基础结构向下游转发websocket请求。

                您可以通过为URI加上lb前缀来平衡websocket的负载,例如lb:ws://serviceid

                经过该过滤器时,会通过ServerWebExchangeUtils.setAlreadyRouted方法把exchange对象标记为“已路由”。

                配置示例如下,通常情况下,除了配置ws,还需要配置一个http的,因为发起websocket连接的请求是http请求。

                # 官方示例
                spring:
                  cloud:
                    gateway:
                      routes:
                      # SockJS route
                      - id: websocket_sockjs_route
                        uri: http://localhost:3001
                        predicates:
                        - Path=/websocket/info/**
                      # Normal Websocket route
                      - id: websocket_route
                        uri: ws://localhost:3001
                        predicates:
                        - Path=/websocket/**
                
                • GatewayMetricsFilter

                  该过滤器主要用于做网关度量监控的,要启用,需添加spring-boot-starter-actuator依赖。然后,默认情况下,只要属性spring.cloud.gateway.metrics.enabled未设置为false,GatewayMetricsFilter就会运行。此过滤器添加一个带有以下标记的计时器度量标准,名为gateway.requests:

                  • routeId: 路由ID.
                  • routeUri: 需要路由的API
                  • outcome: 结果分类参考 HttpStatus.Series.
                  • status: 返回给客户端的http status
                  • httpStatusCode: 返回给客户端的http status
                  • httpMethod: 用户请求的http Method

                    这些指标随后可从/actuator/metrics/gateway.requests中进行抓取,并可轻松地与Prometheus集成以创建Grafana dashboard。

                    要启用prometheus端点,请添加micrometer-registry-prometheus依赖。

                    三、如何自定义全局过滤器🍉

                    在这里演示过滤黑白名单

                    1.在配置文件中定义黑白名单🥝

                    Gateway自定义过滤器——全局过滤器,在这里插入图片描述,第1张

                    #配置端口号
                    server:
                      port: 81
                    #服务名称
                    spring:
                      application:
                        name: qy165-lzq
                    #配置路径规则
                      cloud:
                        gateway:
                          routes:
                            - id: lzq01 #路由的唯一标识-------随便起只要不重复就可以  如果不写默认uuid创建
                              uri: http://localhost:8001  # 真是转发地址
                              predicates:                 # 断言  当满足当前条件时进行跳转到uri  可以理解为判断
                                - Path=/aaa/**
                            - id: lzq02
                              uri: lb://lzq-01      #lb 表示跳转方式 获取nacos上的服务器名
                              predicates:
                                - Path=/aaa/**
                    #连接nacos地址
                        nacos:
                          discovery:
                            server-addr: localhost:8848
                            register-enabled: false
                    filter:
                      whitePaths:
                        - "/product/login"
                        - "/product/aaa"
                        - "/product/bbb"
                      blackPaths:
                        - "/product/ccc"
                    

                    2.定义工具类读取黑白名单🥝

                    Gateway自定义过滤器——全局过滤器,在这里插入图片描述,第2张

                    package com.lzq.filter;
                    import lombok.Data;
                    import org.springframework.boot.context.properties.ConfigurationProperties;
                    import org.springframework.stereotype.Component;
                    import java.util.HashSet;
                    import java.util.Set;
                    @Component
                    @ConfigurationProperties(prefix = "filter")
                    @Data
                    public class FilterUrl {
                         private Set whitePaths=new HashSet<>();
                         private Set blackPaths=new HashSet<>();
                    }
                    

                    3.编写过滤器🥝

                    定义实体类实现接口

                    Gateway自定义过滤器——全局过滤器,在这里插入图片描述,第3张

                    package com.lzq.filter;
                    import com.alibaba.fastjson.JSON;
                    import org.springframework.beans.factory.annotation.Autowired;
                    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
                    import org.springframework.cloud.gateway.filter.GlobalFilter;
                    import org.springframework.core.Ordered;
                    import org.springframework.core.io.buffer.DataBuffer;
                    import org.springframework.http.server.reactive.ServerHttpRequest;
                    import org.springframework.http.server.reactive.ServerHttpResponse;
                    import org.springframework.stereotype.Component;
                    import org.springframework.util.StringUtils;
                    import org.springframework.web.server.ServerWebExchange;
                    import reactor.core.publisher.Mono;
                    import java.nio.charset.StandardCharsets;
                    import java.util.HashMap;
                    import java.util.Map;
                    @Component
                    public class LogFilter implements GlobalFilter, Ordered {
                        @Autowired
                        private FilterUrl filterUrl;
                        @Override
                        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                            ServerHttpRequest request = exchange.getRequest();
                            ServerHttpResponse response = exchange.getResponse();
                            //过滤的业务代码
                            //1.获取请求路径---
                            String path = request.getPath().toString();
                            //判断该路径是否放行路径
                            if(filterUrl.getWhitePaths().contains(path)){
                                return chain.filter(exchange);
                            }
                            //获取请求头
                            String token = request.getHeaders().getFirst("token");
                            if(StringUtils.hasText(token)&&"admin".equals(token)){ //查看redis中是否存在该token
                                return chain.filter(exchange);//放行
                            }
                            //json数据
                            Map map = new HashMap<>();
                            map.put("msg", "未登录");
                            map.put("code", 403);
                            //3.3作JSON转换
                            byte[] bytes = JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
                            //3.4调用bufferFactory方法,生成DataBuffer对象
                            DataBuffer buffer = response.bufferFactory().wrap(bytes);
                            //4.调用Mono中的just方法,返回要写给前端的JSON数据
                            return response.writeWith(Mono.just(buffer));
                        }
                        //Ordered:优先级 值越小 优先级越高
                        @Override
                        public int getOrder() {
                            return 0;
                        }
                    }
                    

                    4.解决跨域问题🥝

                    第一种方式创建实体类

                    Gateway自定义过滤器——全局过滤器,在这里插入图片描述,第4张

                    package com.lzq.config;
                    import org.springframework.context.annotation.Bean;
                    import org.springframework.context.annotation.Configuration;
                    import org.springframework.web.cors.CorsConfiguration;
                    import org.springframework.web.cors.reactive.CorsWebFilter;
                    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
                    import org.springframework.web.util.pattern.PathPatternParser;
                    import java.util.Date;
                    @Configuration
                    public class CorConfig {
                        //处理跨域
                        @Bean
                        public CorsWebFilter corsFilter() {
                            CorsConfiguration config = new CorsConfiguration();
                            config.addAllowedMethod("*");
                            config.addAllowedOrigin("*");
                            config.addAllowedHeader("*");
                            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
                            source.registerCorsConfiguration("/**", config);
                            return new CorsWebFilter(source);
                        }
                    }
                    

                    第二种方式在配置文件中编写

                    Gateway自定义过滤器——全局过滤器,在这里插入图片描述,第5张

                    server:
                      port: 81
                    spring:
                      application:
                        name: gateway  #服务名称
                      cloud:
                        nacos:
                          discovery:
                            server-addr: localhost:8848 #nacos地址
                        gateway:
                          globalcors:
                            add-to-simple-url-handler-mapping: true
                            cors-configurations:
                              '[/**]':  #拦截的请求
                                allowedOrigins: #允许跨域的请求
                                  - "http://localhost:8080"
                                allowedMethods: #运行跨域的请求方式
                                  - "GET"
                                  - "POST"
                                  - "DELETE"
                                  - "PUT"
                                  - "OPTIONS"
                                allowedHeaders: "*" #允许请求中携带的头信息
                                allowedCredentials: true #是否允许携带cookie
                                maxAge: 36000 #跨域检测的有效期,单位s