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本次提交代碼內容


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM