zoukankan      html  css  js  c++  java
  • spring cloud gateway自定义过滤器

    API网关spring cloud gateway和负载均衡框架ribbon实战文章中,主要实现网关与负载均衡等基本功能,详见代码。本节内容将继续围绕此代码展开,主要讲解spring cloud gateway自定义过滤器的功能。本节内容的代码也会提交到GitHub上,注意提交的内容。

    本节主要讲解全局过滤器和局部过滤器。注意下面的示例不能作为生产环境的代码,只是简单的演示自定义过滤器的使用方式,无需纠结实现的功能是否完善。下面主要针对不同的过滤器选择几种场景进行代码演示,不代表某个场景就必须使用全局或者局部的过滤器。

    • 全局过滤器:

    1、限流:每分钟只能访问5次服务

    2、接口用时统计

    • 局部过滤器:

    1、简单的权限检查

    2、指定IP访问

    1、全局过滤器-限流


    本节主要演示全局过滤器的用法:实现 GlobalFilter 和 Ordered,重写相关方法,加入到spring容器管理即可,无需配置,全局过滤器对所有的路由都有效。

    package com.yefengyu.gateway.globalFilter;
    
    import io.github.bucket4j.Bandwidth;
    import io.github.bucket4j.Bucket;
    import io.github.bucket4j.Bucket4j;
    import io.github.bucket4j.Refill;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    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;
    
    import java.time.Duration;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    
    //全局过滤器,实现GlobalFilter接口,和Ordered接口即可。
    @Component
    public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered
    {
        int capacity = 5;//桶的最大容量,即能装载 Token 的最大数量
    
        int refillTokens = 1; //每次 Token 补充量
    
        Duration duration = Duration.ofSeconds(1); //补充 Token 的时间间隔
    
        private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();
    
        private Bucket createNewBucket()
        {
            Refill refill = Refill.greedy(refillTokens, duration);
            Bandwidth limit = Bandwidth.classic(capacity, refill);
            return Bucket4j.builder().addLimit(limit).build();
        }
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip, k -> createNewBucket());
    
            System.out.println("IP: " + ip + ",has Tokens: " + bucket.getAvailableTokens());
            if (bucket.tryConsume(1))
            {
                return chain.filter(exchange);
            }
            else
            {
                exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                return exchange.getResponse().setComplete();
            }
        }
    
        @Override
        public int getOrder()
        {
            return -1000;
        }
    }

    注意在pom.xml文件中加入依赖

    <dependency>
        <groupId>com.github.vladimir-bukhtoyarov</groupId>
        <artifactId>bucket4j-core</artifactId>
        <version>4.4.1</version>
    </dependency>

    2、全局过滤器-统计请求耗时


    只需要在亲请求处理之前和之后标记时间即可。注意此处演示的是使用配置类的形式:

    package com.yefengyu.gateway.globalFilter;
    
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import reactor.core.publisher.Mono;
    
    
    //全局过滤器,使用配置类形式,直接构造bean,使用注解完成Ordered接口功能,统计接口调用时间
    @Configuration
    public class GlobalGatewayFilterConfig
    {
        @Bean
        @Order(-100)
        public GlobalFilter elapsedGlobalFilter()
        {
            return (exchange, chain) -> {
                //调用请求之前统计时间
                Long startTime = System.currentTimeMillis();
                return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
                    //调用请求之后统计时间
                    Long endTime = System.currentTimeMillis();
                    System.out.println(
                        exchange.getRequest().getURI().getRawPath() + ", cost time : " + (endTime - startTime) + "ms");
                }));
            };
        }
    }

    3、局部过滤器-简单的权限检查


    权限检查一般把信息存储在某处,请求到来之后进行核对,有权限的请求将真正执行。

    1、首先编写一个工具类,对权限做管理。

    package com.yefengyu.gateway.utitls;
    
    import java.util.HashMap;
    import java.util.Map;
    
    
    public final class AuthUtil
    {
        private static Map<String, String> map = new HashMap<>();
    
        private AuthUtil()
        {
        }
    
        //程序启动的时候加载权限的信息,比如从文件、数据库中加载
        public static void init()
        {
            map.put("tom", "123456");
        }
    
        //简单判断
        public static boolean isPermitted(String name, String password)
        {
            return map.containsKey(name) && map.get(name).equals(password);
        }
    
    }

    我们简单的将权限信息放到map中保管,init方法是初始化方法,isPermitted是对外提供一个判断是否有权限的方法。

    2、服务启动的时候,需要初始化权限map,因此主启动类进行了修改:

    package com.yefengyu.gateway;
    
    import com.yefengyu.gateway.utitls.AuthUtil;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.context.event.ApplicationStartedEvent;
    import org.springframework.context.ApplicationListener;
    
    
    @SpringBootApplication
    public class GatewayApplication
    {
        public static void main(String[] args)
        {
            SpringApplication springApplication = new SpringApplication(GatewayApplication.class);
            springApplication.addListeners(new ApplicationListenerStarted());//增加监听器
            springApplication.run(args);
        }
    
        private static class ApplicationListenerStarted
            implements ApplicationListener<ApplicationStartedEvent>
        {
            @Override
            public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent)
            {
                //权限初始化数据
                AuthUtil.init();
            }
        }
    }

    3、编写一个局部过滤器,需要实现GatewayFilter, Ordered,实现相关的方法

    package com.yefengyu.gateway.localFilter;
    
    import com.yefengyu.gateway.utitls.AuthUtil;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    
    public class AuthGatewayFilter implements GatewayFilter, Ordered
    {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
        {
            //获取header的参数
            String name = exchange.getRequest().getHeaders().getFirst("name");
            String password = exchange.getRequest().getHeaders().getFirst("password");
            boolean permitted = AuthUtil.isPermitted(name, password);//权限比较
            if (permitted)
            {
                return chain.filter(exchange);
            }
            else
            {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
        }
    
        @Override
        public int getOrder()
        {
            return 10;
        }
    }

    4、接着需要把上面自定义的局部过滤器加入到过滤器工厂,并且注册到spring容器中。

    package com.yefengyu.gateway.localFilter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.stereotype.Component;
    
    
    @Component
    public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
    {
        @Override
        public GatewayFilter apply(Object config)
        {
            return new AuthGatewayFilter();
        }
    }

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

    image

    4、局部过滤器-指定IP访问


    我们的需求是如果在配置文件配置了一个IP,那么该ip就可以访问,其它IP通通不能访问。如果不使用该过滤器,那么所有IP都可以访问服务。

    这里我们看到上面的AuthGatewayFilter和AuthGatewayFilterFactory代码本来就是为了同一个过滤器规则编写的两个类,如果过滤器规则很多,那么类文件就很多,其实这两个类可以合并,并且还会提供其它的功能:

    package com.yefengyu.gateway.localFilter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.List;
    
    
    @Component
    @Order(99)
    public class IPForbidGatewayFilterFactory
        extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config>
    {
    
        public IPForbidGatewayFilterFactory()
        {
            super(Config.class);
        }
    
        @Override
        public List<String> shortcutFieldOrder()
        {
            return Arrays.asList("forbidIp");
        }
    
        @Override
        public GatewayFilter apply(Config config)
        {
            return (exchange, chain) -> {
                String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
                if (config.getForbidIp().equals(ip))
                {
                    return chain.filter(exchange);
                }
                exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                return exchange.getResponse().setComplete();
    
            };
        }
    
        static public class Config
        {
            private String forbidIp;
    
            public String getForbidIp()
            {
                return forbidIp;
            }
    
            public void setForbidIp(String forbidIp)
            {
                this.forbidIp = forbidIp;
            }
        }
    }

    Config类定义了一个属性,要重写List<String> shortcutFieldOrder()这个方法指定属性名称。规则逻辑很简单,判断配置文件中的ip是和请求来的ip是否相同,相同则可以访问服务。

    配置文件:

    image

    正常测试

    image

    5、总结


        全局过滤器,对所有的路由都有效,所有不用在配置文件中配置,主要实现了GlobalFilter 和 Ordered接口,并将过滤器注册到spring 容器。由于使用java配置类的方式也可以注册bean,所有也可以使用配置类的方式,Ordered接口使用Order注解代替,GlobalFilter 只是个接口,可以使用Lambda表达式替换。

        局部过滤器,需要在配置文件中配置,如果配置,则该过滤器才会生效。主要实现GatewayFilter, Ordered接口,并通过AbstractGatewayFilterFactory的子类注册到spring容器中,当然也可以直接继承AbstractGatewayFilterFactory,在里面写过滤器逻辑,还可以从配置文件中读取外部数据。

        本节代码已提交:github本次提交代码内容

  • 相关阅读:
    lambda表达式
    netstat
    【makfile | 资源】网址链接
    【makefile】 $@ $^ %< 的使用

    【顺序容器 || 09】
    标准IO库
    我使出这“三板斧”(分段锁、哈希锁、弱引用锁)灭霸跑了......
    JAVA 线上故障排查完整套路,从 CPU、磁盘、内存、网络、GC 一条龙!
    一条 SQL 引发的事故,同事直接被开除!
  • 原文地址:https://www.cnblogs.com/ye-feng-yu/p/11111803.html
Copyright © 2011-2022 走看看