本篇文章爲系列文章,未讀第一集的同學請猛戳這裏:
本篇文章講解 Gateway 網關過濾器和全局過濾器以及自定義過濾器。
過濾器
Spring Cloud Gateway 根據作用範圍劃分爲 GatewayFilter
和 GlobalFilter
,二者區別如下:
GatewayFilter
:網關過濾器,需要通過spring.cloud.routes.filters
配置在具體路由下,只作用在當前路由上或通過spring.cloud.default-filters
配置在全局,作用在所有路由上。GlobalFilter
:全局過濾器,不需要在配置文件中配置,作用在所有的路由上,最終通過GatewayFilterAdapter
包裝成GatewayFilterChain
可識別的過濾器,它爲請求業務以及路由的 URI 轉換爲真實業務服務請求地址的核心過濾器,不需要配置系統初始化時加載,並作用在每個路由上。
網關過濾器 GatewayFilter
點擊鏈接觀看:網關過濾器視頻(獲取更多請關注公衆號「哈嘍沃德先生」)
網關過濾器用於攔截並鏈式處理 Web 請求,可以實現橫切與應用無關的需求,比如:安全、訪問超時的設置等。修改傳入的 HTTP 請求或傳出 HTTP 響應。Spring Cloud Gateway 包含許多內置的網關過濾器工廠一共有 22 個,包括頭部過濾器、 路徑類過濾器、Hystrix 過濾器和重寫請求 URL 的過濾器, 還有參數和狀態碼等其他類型的過濾器。根據過濾器工廠的用途來劃分,可以分爲以下幾種:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。
接下來我們舉例說明其中一部分如何使用,其餘等大家工作中需要應用時再查詢資料學習或者諮詢我也可以。
Path 路徑過濾器
Path 路徑過濾器可以實現 URL 重寫,通過重寫 URL 可以實現隱藏實際路徑提高安全性,易於用戶記憶和鍵入,易於被搜索引擎收錄等優點。實現方式如下:
RewritePathGatewayFilterFactory
RewritePath 網關過濾器工廠採用路徑正則表達式參數和替換參數,使用 Java 正則表達式來靈活地重寫請求路徑。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/product/**, /api-gateway/**
filters: # 網關過濾器
# 將 /api-gateway/product/1 重寫爲 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
訪問:http://localhost:9000/api-gateway/product/1 結果如下:
PrefixPathGatewayFilterFactory
PrefixPath 網關過濾器工廠爲匹配的 URI 添加指定前綴。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/**
filters: # 網關過濾器
# 將 /1 重寫爲 /product/1
- PrefixPath=/product
訪問:http://localhost:9000/1 結果如下:
StripPrefixGatewayFilterFactory
StripPrefix 網關過濾器工廠採用一個參數 StripPrefix,該參數表示在將請求發送到下游之前從請求中剝離的路徑個數。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/**
filters: # 網關過濾器
# 將 /api/123/product/1 重寫爲 /product/1
- StripPrefix=2
訪問:http://localhost:9000/api/123/product/1 結果如下:
SetPathGatewayFilterFactory
SetPath 網關過濾器工廠採用路徑模板參數。 它提供了一種通過允許模板化路徑段來操作請求路徑的簡單方法,使用了 Spring Framework 中的 uri 模板,允許多個匹配段。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api/product/{segment}
filters: # 網關過濾器
# 將 /api/product/1 重寫爲 /product/1
- SetPath=/product/{segment}
訪問:http://localhost:9000/api/product/1 結果如下:
Parameter 參數過濾器
AddRequestParameter 網關過濾器工廠會將指定參數添加至匹配到的下游請求中。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api-gateway/**
filters: # 網關過濾器
# 將 /api-gateway/product/1 重寫爲 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
# 在下游請求中添加 flag=1
- AddRequestParameter=flag, 1
修改商品服務的控制層代碼。
package com.example.controller;
import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 根據主鍵查詢商品
*
* @param id
* @return
*/
@GetMapping("/{id}")
public Product selectProductById(@PathVariable("id") Integer id, String flag) {
System.out.println("flag = " + flag);
return productService.selectProductById(id);
}
}
訪問:http://localhost:9000/api-gateway/product/1 控制檯結果如下:
flag = 1
Status 狀態過濾器
SetStatus 網關過濾器工廠採用單個狀態參數,它必須是有效的 Spring HttpStatus。它可以是整數 404 或枚舉 NOT_FOUND 的字符串表示。
spring:
application:
name: gateway-server # 應用名稱
cloud:
gateway:
# 路由規則
routes:
- id: product-service # 路由 ID,唯一
uri: lb://product-service # lb:// 根據服務名稱從註冊中心獲取服務請求地址
predicates: # 斷言(判斷條件)
# 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
- Path=/api-gateway/**
filters: # 網關過濾器
# 將 /api-gateway/product/1 重寫爲 /product/1
- RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
# 任何情況下,響應的 HTTP 狀態都將設置爲 404
- SetStatus=404 # 404 或者對應的枚舉 NOT_FOUND
訪問:http://localhost:9000/api-gateway/product/1 結果如下:
全局過濾器 GlobalFilter
全局過濾器不需要在配置文件中配置,作用在所有的路由上,最終通過 GatewayFilterAdapter 包裝成 GatewayFilterChain 可識別的過濾器,它是請求業務以及路由的 URI 轉換爲真實業務服務請求地址的核心過濾器,不需要配置系統初始化時加載,並作用在每個路由上。
自定義過濾器
即使 Spring Cloud Gateway
自帶許多實用的 GatewayFilter Factory、Gateway Filter、Global Filter
,但是在很多情景下我們仍然希望可以自定義自己的過濾器,實現一些騷操作。
自定義網關過濾器
自定義網關過濾器需要實現以下兩個接口 :GatewayFilter
,Ordered
。
創建過濾器
CustomGatewayFilter.java
package com.example.filter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定義網關過濾器
*/
public class CustomGatewayFilter implements GatewayFilter, Ordered {
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義網關過濾器被執行");
return chain.filter(exchange); // 繼續向下執行
}
/**
* 過濾器執行順序,數值越小,優先級越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
註冊過濾器
package com.example.config;
import com.example.filter.CustomGatewayFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 網關路由配置類
*/
@Configuration
public class GatewayRoutesConfiguration {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r
// 斷言(判斷條件)
.path("/product/**")
// 目標 URI,路由到微服務的地址
.uri("lb://product-service")
// 註冊自定義網關過濾器
.filters(new CustomGatewayFilter())
// 路由 ID,唯一
.id("product-service"))
.build();
}
}
訪問
註釋配置文件中所有網關配置,重啓並訪問:http://localhost:9000/product/1 控制檯結果如下:
自定義網關過濾器被執行
自定義全局過濾器
自定義全局過濾器需要實現以下兩個接口 :GlobalFilter
,Ordered
。通過全局過濾器可以實現權限校驗,安全性驗證等功能。
創建過濾器
實現指定接口,添加 @Component
註解即可。
CustomGlobalFilter.java
package com.example.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定義全局過濾器
*/
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("自定義全局過濾器被執行");
return chain.filter(exchange); // 繼續向下執行
}
/**
* 過濾器執行順序,數值越小,優先級越高
*
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
訪問
訪問:http://localhost:9000/product/1 控制檯結果如下:
自定義全局過濾器被執行
統一鑑權
接下來我們在網關過濾器中通過 token 判斷用戶是否登錄,完成一個統一鑑權案例。
創建過濾器
AccessFilter.java
package com.example.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 權限驗證過濾器
*/
@Component
public class AccessFilter implements GlobalFilter, Ordered {
private Logger logger = LoggerFactory.getLogger(AccessFilter.class);
/**
* 過濾器業務邏輯
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 獲取請求參數
String token = exchange.getRequest().getQueryParams().getFirst("token");
// 業務邏輯處理
if (null == token) {
logger.warn("token is null...");
ServerHttpResponse response = exchange.getResponse();
// 響應類型
response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
// 響應狀態碼,HTTP 401 錯誤代表用戶沒有訪問權限
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 響應內容
String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}";
DataBuffer buffer = response.bufferFactory().wrap(message.getBytes());
// 請求結束,不在繼續向下請求
return response.writeWith(Mono.just(buffer));
}
// 使用 token 進行身份驗證
logger.info("token is OK!");
return chain.filter(exchange);
}
/**
* 過濾器執行順序,數值越小,優先級越高
*
* @return
*/
@Override
public int getOrder() {
return 1;
}
}
訪問
訪問:http://localhost:9000/product/1 結果如下:
訪問:http://localhost:9000/product/1?token=abc123 結果如下:
下一篇我們講解 Gateway 網關如何實現限流、整合Sentinel實現限流以及高可用網關環境搭建,記得關注噢~
本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議
。
大家可以通過 分類
查看更多關於 Spring Cloud
的文章。
🤗 您的點贊
和轉發
是對我最大的支持。
📢 掃碼關注 哈嘍沃德先生
「文檔 + 視頻」每篇文章都配有專門視頻講解,學習更輕鬆噢 ~