天天看點

Spring Cloud 學習筆記七:搭建微服務工程之Gateway 全局過濾器(GlobalFilter)Gateway 全局過濾器(GlobalFilter)

目錄

Gateway 全局過濾器(GlobalFilter)

Gateway 全局過濾器(GlobalFilter)

全局過濾器作用于所有的路由,不需要單獨配置,我們可以用它來實作很多統一化處理的業務需求,比如權限認證、IP 通路限制等。

接口定義類

org.springframework.cloud.gateway.filter.GlobalFilter

,具體代碼如下所示:

public interface GlobalFilter {
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
           

Spring Cloud Gateway 自帶的

GlobalFilter

實作類有很多:

Spring Cloud 學習筆記七:搭建微服務工程之Gateway 全局過濾器(GlobalFilter)Gateway 全局過濾器(GlobalFilter)

GlobalFilter 實作類

有轉發、路由、負載等相關的

GlobalFilter

,感興趣的話可以去看下源碼自行了解。我們如何通過定義 GlobalFilter 來實作我們的業務邏輯?

當請求到來時,Filtering Web Handler 處理器會添加所有 GlobalFilter 執行個體和比對的 GatewayFilter 執行個體到過濾器鍊中。

過濾器鍊會使用

org.springframework.core.Ordered

注解所指定的順序,進行排序。Spring Cloud Gateway 區分了過濾器邏輯執行的

pre

post

階段,是以優先級高的過濾器将會在

pre

階段最先執行,優先級最低的過濾器則在

post

階段最後執行。代碼如下所示:

@Bean
    @Order(-1)
    public GlobalFilter a() {
        return (exchange, chain) -> {

            System.out.println("first pre filter");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                System.out.println("third post filter");
            }));
        };
    }
    @Bean
    @Order(0)
    public GlobalFilter b() {
        return (exchange, chain) -> {
            System.out.println("second pre filter");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                System.out.println("second post filter");
            }));
        };
    }
    @Bean
    @Order(1)
    public GlobalFilter c() {
        return (exchange, chain) -> {
            System.out.println("third pre filter");
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                System.out.println("first post filter");
            }));
        };
    }
           

上面定義了 3 個

GlobalFilter

,通過

@Order

來指定執行的順序,數字越小,優先級越高。下面就是輸出的日志,從日志就可以看出執行的順序,如下所示:

first pre filter
second pre filter
third pre filter
first post filter
second post filter
third post filter
           

GlobalFilter

的邏輯比較多時,推薦大家單獨寫一個

GlobalFilter

來處理,比如我們要實作對 IP 的通路限制,即不在 IP 白名單中就不能調用的需求。單獨定義隻需要實作

GlobalFilter

Ordered

兩個接口就可以了,具體代碼如下所示:

@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

    // IP黑名單清單
    private List<String> blackIpList = Arrays.asList("172.0.0.1", "169.254.192.136", "192.168.231.1");

    @Override
    public int getOrder() {
        return 0;
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String ip = getIP(exchange.getRequest());
        if (blackIpList.contains(ip)) {
            ServerHttpResponse response = exchange.getResponse();
            ObjectMapper mapper = new ObjectMapper();
            Map<String, Object> data = new HashMap<String, Object>(16);
            data.put("msg", "非法請求");
            data.put("code", 999999);
            byte[] json = null;
            try {
                json = mapper.writeValueAsBytes(data);
            }catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            DataBuffer buffer = response.bufferFactory().wrap(json);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }

    /**
     * 從請求頭中擷取使用者的實際IP,根據Nginx轉發的請求頭擷取
     * @param request
     * @return
     */
    private String getIP(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ipAddress = headers.getFirst("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = Optional.ofNullable(request.getRemoteAddress())
                    .map(address -> address.getAddress().getHostAddress())
                    .orElse("");
            if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
                // 根據網卡取本機配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    // ignore
                }
            }
        }
        // 對于通過多個代理的情況,第一個IP為用戶端真實IP,多個IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) {
            int index = ipAddress.indexOf(",");
            if (index > 0) {
                ipAddress = ipAddress.substring(0, index);
            }
        }
        return ipAddress;
    }
}
           

通路 http://localhost:8011/study/index.html,就可以看到自定義響應資料。

過濾的使用雖然比較簡單,但作用很大,可以處理很多需求,上面講的 IP 認證攔截隻是冰山一角,更多的功能需要我們自己基于過濾器去實作。