一、前言
隨着業務的擴展,微服務會不對增加,相應的其對外開放的 API 接口也勢必增多,這不利於前端的調用以及不同場景下數據的返回,因此,我們通常都需要設計一個 API 網關作爲一個統一的 API 入口,來組合一個或多個內部 API。
二、簡單介紹
# 2.1 API 網關使用場景
黑白名單: 實現通過 IP 地址控制請求的訪問
日誌:實現訪問日誌的記錄,進而實現日誌分析,處理性能指標等
協議適配:實現通信協議的校驗、適配轉換的功能
身份認證:對請求進行身份認證
計流限流:可以設計限流規則,記錄訪問流量
路由:將請求進行內部(服務)轉發
# 2.2 API 網關的實現
業界常用的 API 網關有很多方式,如:Spring Cloud Zuul、 Nginx、Tyk、Kong。本篇介紹的對象正是 Spring Cloud Zuul。
Zuul 是 Netflix 公司開源的一個 API 網關組件,提供了認證、鑑權、限流、動態路由、監控、彈性、安全、負載均衡、協助單點壓測等邊緣服務的框架。
Spring Cloud Zuul 是基於 Netflix Zuul 的微服務路由和過濾器的解決方案,也用於實現 API 網關。其中,路由功能負責將外部請求轉發到具體的微服務實例上,是實現外部訪問統一入門的基礎。而過濾功能是負責對請求的處理過程進行干預,是實現請求校驗、服務聚合等功能的基礎。
Spring Cloud Zuul 和 Eureka 進行整合時,Zuul 將自身註冊到 Eureka 服務中,同時從 Eureka 中獲取其他微服務信息,以便請求可以準確的通過 Zuul 轉發到具體微服務上。
三、實戰演練
本次測試案例基於之前發表的文章中介紹的案例進行演示,不清楚的讀者請先轉移至 《Spring Cloud 入門 之 Hystrix 篇(四)》 進行瀏覽。
當前的項目列表如下:
服務實例 | 端口 | 描述 |
---|---|---|
common-api | - | 公用的 api,如:實體類 |
eureka-server | 9000 | 註冊中心(Eureka 服務端) |
goods-server | 8081 | 商品服務(Eureka 客戶端) |
goods-server-02 | 8082 | 商品服務(Eureka 客戶端) |
goods-server-03 | 8083 | 商品服務(Eureka 客戶端) |
order-server | 8100 | 訂單服務(Eureka 客戶端) |
創建一個爲名 gateway-server 的 Spring Boot 項目。
# 3.1 添加依賴
<!-- eureka 客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- zuul 網關 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
# 3.2 配置文件
server:
port: 9600
spring:
application:
name: gateway
eureka:
instance:
instance-id: gateway-9600
prefer-ip-address: true
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
# 3.3 啓動 Zuul
在啓動類上添加 @EnableZuulProxy 註解:
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
啓動上邊的所有項目,打開 Postman 請求訂單下單接口,如下圖:
圖中,我們首先不經過網關直接訪問 order-server 項目請求地址:http://localhost:8100/order/place
之後再修改成訪問 gateway-server 項目的請求地址:http://localhost:9600/order/order/place
最終,響應結果都一樣。
提示:http://localhost:9600/order/order/place 中第一個 order 表示的是註冊在 Eureka 上的訂單服務名稱。
# 3.4 zuul 常用配置
修改路由:
zuul:
sensitive-headers: # 全局忽略敏感頭,即允許接收 cookie 等請求頭信息
routes:
extlight: # 任意名字,保證唯一即可
path: /extlight/** # 自定義,真正用到的請求地址
service-id: ORDER # 路由到的目標服務名稱
將訂單服務的路由名稱改成 extlight。
使用 Postman 請求下單接口,運行結果:
請求成功。
禁用路由:
zuul:
ignored-patterns:
- /order/order/**
http://localhost:9600/order/order/place 無法被正常路由到訂單服務,響應返回 404。
路由加前綴:
zuul:
prefix: /api
所有請求中的 path 需要添加 api 前綴。如: http://localhost:9600/extlight/order/place 需要改成 http://localhost:9600/api/extlight/order/place。
設置敏感頭:
zuul:
sensitive-headers: # 設置全局敏感頭,如果爲空,表示接收所有敏感頭信息
或
zuul:
routes:
extlight: # 任意名字,保證唯一即可
path: /extlight/** # 自定義,真正用到的請求地址
service-id: ORDER # 路由到的目標服務名稱
sensitive-headers: # 針對 /extlight/ 的請求設置敏感頭信息
四、Zuul 自定義過濾器
Zuul 的核心技術就是過濾器,該框架提供了 ZuulFilter 接口讓開發者可以自定義過濾規則。
我們以身份檢驗爲例,自定義 ZuulFilter 過濾器實現該功能。
# 4.1 創建用戶服務
新建名爲 user-server 的項目。
添加依賴:
<!-- common api -->
<dependency>
<groupId>com.extlight.springcloud</groupId>
<artifactId>common-api</artifactId>
<version>${parent-version}</version>
</dependency>
<!-- springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka 客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml:
server:
port: 8200
spring:
application:
name: USER
eureka:
instance:
instance-id: user-api-8200
prefer-ip-address: true # 訪問路徑可以顯示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
登錄接口:
@RestController
@RequestMapping("/user")
public class LoginController {
@PostMapping("/login")
public Result login(String username, String password, HttpServletResponse response) {
if ("admin".equals(username) && "admin".equals(password)) {
// 模擬生成 token,實際開發中 token 應存放在數據庫或緩存中
String token = "123456";
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
cookie.setMaxAge(60 * 10);
response.addCookie(cookie);
return Result.success();
}
return Result.fail(401, "賬號或密碼錯誤");
}
}
user-server 啓動類:
@EnableEurekaClient
@SpringBootApplication
public class UserServerApplication {
public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);
}
}
# 4.2 創建 ZuulFilter 過濾器
在 gateway-server 項目中,新建一個過濾器,需要繼承 ZuulFilter 類:
@Component
public class AuthenticationFilter extends ZuulFilter {
/**
* 是否開啓過濾
*/
@Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
boolean flag = request.getRequestURI().contains("/login");
// 如果是登錄請求不進行過濾
if (flag) {
System.out.println("========不執行 zuul 過濾方法=======");
} else {
System.out.println("========執行 zuul 過濾方法=======");
}
return !flag;
}
/**
* 過濾器執行內容
*/
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String token = request.getParameter("token");
// 此處模擬獲取數據庫或緩存中的 token
String dbToken = "123456";
// 此處簡單檢驗 token
if (token == null || "".equals(token) || !dbToken.equals(token)) {
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
/**
* 過濾器類型
*/
@Override
public String filterType() {
return "pre";
}
/**
* 過濾器執行順序
*/
@Override
public int filterOrder() {
return 0;
}
}
其中,filterType 有 4 種類型:
pre: 這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等。
routing:這種過濾器將請求路由到微服務。這種過濾器用於構建發送給微服務的請求,並使用 Apache HttpClient 或 Netfilx Ribbon 請求微服務。
post:這種過濾器在路由到微服務以後執行。這種過濾器可用來爲響應添加標準的 HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
error:在其他階段發生錯誤時執行該過濾器。
其過濾順序如下圖:
# 4.3 測試過濾器
運行所有項目,測試操作步驟如下:
請求用戶服務的登錄接口(http://localhost:9600/user/user/login),請求不執行 zuul 過濾方法,並且請求響應返回的 cookie 包含 token
請求訂單服務的下單接口(http://localhost:9600/extlight/order/place),但不攜帶 token,請求需要執行 zuul 過濾方法,請求響應 401 權限不足
請求訂單服務的下單接口(http://localhost:9600/extlight/order/place),攜帶之前登錄接口返回的 token,請求需要執行 zuul 過濾方法,校驗通過
測試效果圖如下:
五、案例源碼
六、參考資料
Announcing Zuul: Edge Service in the Cloud
- 本文作者: MoonlightL
- 本文鏈接: https://www.extlight.com/2019/03/25/Spring-Cloud-入門-之-Zuul-篇(五)/
- 版權聲明: 本博客所有文章除特別聲明外均爲原創,採用 CC BY-NC-SA 4.0 許可協議。轉載請在文章開頭明顯位置註明原文鏈接和作者等相關信息,明確指出修改(如有),並通過 E-mail 等方式告知,謝謝合作!