[TOC]
上一篇文章中,講了Zuul 轉發,動態路由,負載均衡,等等一些Zuul 的特性,這個一篇文章,講Zuul Filter 使用,關于網關的作用,這里就不再次贅述了,重點是zuul的Filter ,我們可以實現安全控制,比如,只有請求參數中有token和密碼的客戶端才能訪問服務端的資源。那么如何來實現Filter了?
# Spring Cloud Zuul
## zuul 執行流程
![執行流程圖][11]
**Zuul**:大部分功能都是通過過濾器來實現的。Zuul中定義了四種標準過濾器類型,這些過濾器類型對應于請求的典型生命周期。
**PRE**:這種過濾器在請求被路由之前調用。我們可利用這種過濾器實現身份驗證、在集群中選擇請求的微服務、記錄調試信息等。
**ROUTING**:這種過濾器將請求路由到微服務。這種過濾器用于構建發送給微服務的請求,并使用Apache HttpClient或Netfilx Ribbon請求微服務。
**OST**:這種過濾器在路由到微服務以后執行。這種過濾器可用來為響應添加標準的HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。
**ERROR**:在其他階段發生錯誤時執行該過濾器。
除了默認的過濾器類型,Zuul還允許我們創建自定義的過濾器類型。例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應,而不將請求轉發到后端的微服務。
## 準備工作
我們先拿之前兩篇文章,構建的兩個微服務代碼為基礎,進行下面的操作
**建議先閱讀以下兩篇文章**
[Spring Cloud(四) 服務提供者 Eureka + 服務消費者 Feign ](http://www.ymq.io/2017/12/06/spring-cloud-feign/)
[Spring Cloud(三) 服務提供者 Eureka + 服務消費者(rest + Ribbon)](http://www.ymq.io/2017/12/05/spring-cloud-ribbon-rest/)
[http://www.ymq.io/2017/12/06/spring-cloud-feign/](http://www.ymq.io/2017/12/06/spring-cloud-feign/)
[http://www.ymq.io/2017/12/05/spring-cloud-ribbon-rest/](http://www.ymq.io/2017/12/05/spring-cloud-ribbon-rest/)
## Eureka Service
**導入第三篇文章中的項目:作為服務注冊中心**
`spring-cloud-eureka-service`
## Eureka Provider
**導入第三篇文章中的項目:作為服務的提供者**
`spring-cloud-eureka-provider-1`
`spring-cloud-eureka-provider-2`
`spring-cloud-eureka-provider-3`
## 簡單使用
**新建項目** `spring-cloud-zuul-filter`
### 添加依賴
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
```
### 開啟服務注冊
在程序的啟動類 `ZuulFilterApplication` 通過 `@EnableZuulProxy` 開啟 Zuul 服務網關
```java
package io.ymq.example.zuul.filter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@EnableZuulProxy
@SpringBootApplication
public class ZuulFilterApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFilterApplication.class, args);
}
}
```
### 添加配置
配置文件 `application.yml`
```sh
spring:
application:
name: zuul-service-filter
server:
port: 9000
zuul:
routes:
api:
path: /**
serviceId: eureka-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
```
### TokenFilter
`ZuulFilter` 是Zuul中核心組件,通過繼承該抽象類,覆寫幾個關鍵方法達到自定義調度請求的作用
**TokenFilter 過濾器**
```java
package io.ymq.example.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
/**
* 描述: 過濾器 token
*
* @author yanpenglei
* @create 2017-12-11 14:38
**/
public class TokenFilter extends ZuulFilter {
private final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class);
@Override
public String filterType() {
return "pre"; // 可以在請求被路由之前調用
}
@Override
public int filterOrder() {
return 0; // filter執行順序,通過數字指定 ,優先級為0,數字越大,優先級越低
}
@Override
public boolean shouldFilter() {
return true;// 是否執行該過濾器,此處為true,說明需要過濾
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
LOGGER.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");// 獲取請求的參數
if (StringUtils.isNotBlank(token)) {
ctx.setSendZuulResponse(true); //對請求進行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false); //不對其進行路由
ctx.setResponseStatusCode(400);
ctx.setResponseBody("token is empty");
ctx.set("isSuccess", false);
return null;
}
}
}
```
### PasswordFilter
`ZuulFilter` 是Zuul中核心組件,通過繼承該抽象類,覆寫幾個關鍵方法達到自定義調度請求的作用
**PasswordFilter 過濾器**
```java
package io.ymq.example.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
/**
* 描述: 過濾器 Password
*
* @author yanpenglei
* @create 2017-12-11 15:40
**/
public class PasswordFilter extends ZuulFilter {
private final Logger LOGGER = LoggerFactory.getLogger(TokenFilter.class);
@Override
public String filterType() {
return "post"; // 請求處理完成后執行的filter
}
@Override
public int filterOrder() {
return 1; // 優先級為0,數字越大,優先級越低
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return (boolean) ctx.get("isSuccess");
// 判斷上一個過濾器結果為true,否則就不走下面過濾器,直接跳過后面的所有過濾器并返回 上一個過濾器不通過的結果。
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
LOGGER.info("--->>> PasswordFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String username = request.getParameter("password");
if (null != username && username.equals("123456")) {
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(400);
ctx.setResponseBody("The password cannot be empty");
ctx.set("isSuccess", false);
return null;
}
}
}
```
### 開啟過濾器
在程序的啟動類 `ZuulFilterApplication` 添加 Bean
```java
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
@Bean
public PasswordFilter PasswordFilter() {
return new PasswordFilter();
}
```
### filterType
**filterType:返回一個字符串代表過濾器的類型,在zuul中定義了四種不同生命周期的過濾器類型,具體如下:**
- pre:路由之前
- routing:路由之時
- post: 路由之后
- error:發送錯誤調用
- filterOrder:過濾的順序
- shouldFilter:這里可以寫邏輯判斷,是否要過濾,本文true,永遠過濾。
- run:過濾器的具體邏輯。可用很復雜,包括查sql,nosql去判斷該請求到底有沒有權限訪問。
## 測試服務
依次啟動項目:
`spring-cloud-eureka-service`
`spring-cloud-eureka-provider-1`
`spring-cloud-eureka-provider-2`
`spring-cloud-eureka-provider-3`
`spring-cloud-zuul-filter`
啟動該工程后,訪問服務注冊中心,查看服務是否都已注冊成功:[http://localhost:8761/](http://localhost:8761/)
![查看各個服務注冊狀態][22]
**查看 eureka 監控,看服務是否都注冊成功**
### token 測試
訪問:[http://127.0.0.1:8761/](http://127.0.0.1:8761/)
**步驟一** 提示 `token is empty`
訪問:[http://127.0.0.1:9000/](http://127.0.0.1:9000/)
![瀏覽器訪問][33]
**步驟二** 加上token `?token=token-uuid` ,已經驗證通過了,提示 `The password cannot be empty`
訪問:[http://127.0.0.1:9000/?token=token-uuid](http://127.0.0.1:9000/?token=token-uuid)
![token is empty][44]
### password 測試
加上`token` 和 `password` `&password=123456` ,已經驗證通過
訪問:[http://127.0.0.1:9000/?token=token-uuid&password=123456](http://127.0.0.1:9000/?token=token-uuid&password=123456)
F5 刷新,每次都驗證通過,并且負載均衡
![The password cannot be empty][55]
[11]: https://www.souyunku.com/images/2017/SpringCloud/zuulFilter/11.png
[22]: https://www.souyunku.com/images/2017/SpringCloud/zuulFilter/22.png
[33]: https://www.souyunku.com/images/2017/SpringCloud/zuulFilter/33.png
[44]: https://www.souyunku.com/images/2017/SpringCloud/zuulFilter/44.png
[55]: https://www.souyunku.com/images/2017/SpringCloud/zuulFilter/55.png
## 源碼下載
**GitHub:**[https://github.com/souyunku/spring-cloud-examples/tree/master/spring-cloud-zuul-filter](https://github.com/souyunku/spring-cloud-examples/tree/master/spring-cloud-zuul-filter)
**碼云:**[https://gitee.com/souyunku/spring-cloud-examples/tree/master/spring-cloud-zuul-filter](https://gitee.com/souyunku/spring-cloud-examples/tree/master/spring-cloud-zuul-filter)
- Spring Cloud(一)服務的注冊與發現(Eureka)
- Spring Cloud(二)Consul 服務治理實現
- Spring Cloud(三)服務提供者 Eureka + 服務消費者(rest + Ribbon)
- Spring Cloud(四)服務提供者 Eureka + 服務消費者 Feign
- Spring Cloud(五)斷路器監控(Hystrix Dashboard)
- Spring Cloud(六)服務網關 zuul 快速入門
- Spring Cloud(七)服務網關 Zuul Filter 使用
- Spring Cloud(八)高可用的分布式配置中心 Spring Cloud Config
- Spring Cloud(九)高可用的分布式配置中心 Spring Cloud Config 集成 Eureka 服務
- Spring Cloud(十)高可用的分布式配置中心 Spring Cloud Config 中使用 Refresh
- Spring Cloud(十一)高可用的分布式配置中心 Spring Cloud Bus 消息總線集成(RabbitMQ)