目錄
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
實作類有很多:
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 認證攔截隻是冰山一角,更多的功能需要我們自己基于過濾器去實作。