在上篇文章中咱們瞭解了 Spring Cloud Zuul 做爲網關所具有的最基本功能:路由(Router),下面咱們將關注 Spring Cloud Zuul 的另外一核心功能:過濾器(Filter)。html
咱們已經可以實現請求的路由功能,因此咱們的微服務應用提供的接口就能夠經過統一的 API 網關入口被客戶端訪問到了。
可是,每一個客戶端用戶請求微服務應用提供的接口時,它們的訪問權限每每都須要有必定的限制,系統並不會將全部的微服務接口都對它們開放。然而,目前的服務路由並無限制權限這樣的功能,全部請求都會被毫無保留地轉發到具體的應用並返回結果。
爲了實現對客戶端請求的安全校驗和權限控制,最簡單和粗暴的方法就是爲每一個微服務應用都實現一套用於校驗簽名和鑑別權限的過濾器或攔截器。不過,這樣的作法並不可取,它會增長往後的系統維護難度,由於同一個系統中的各類校驗邏輯不少狀況下都是大體相同或相似的,這樣的實現方式會使得類似的校驗邏輯代碼被分散到了各個微服務中去,冗餘代碼的出現是咱們不但願看到的。因此,比較好的作法是將這些校驗邏輯剝離出去,構建出一個獨立的鑑權服務。在完成了剝離以後,有很多開發者會直接在微服務應用中經過調用鑑權服務來實現校驗,可是這樣的作法僅僅只是解決了鑑權邏輯的分離,並無在本質上將這部分不屬於業餘的邏輯拆分出原有的微服務應用,冗餘的攔截器或過濾器依然會存在。java
對於這樣的問題,更好的作法是經過前置的網關服務來完成這些非業務性質的校驗。因爲網關服務的加入,外部客戶端訪問咱們的系統已經有了統一入口,既然這些校驗與具體業務無關,那何不在請求到達的時候就完成校驗和過濾,而不是轉發後再過濾而致使更長的請求延遲。同時,經過在網關中完成校驗和過濾,微服務應用端就能夠去除各類複雜的過濾器和攔截器了,這使得微服務應用的接口開發和測試複雜度也獲得了相應的下降。spring
Filter 的生命週期有 4 個,分別是 「PRE」、「ROUTING」、「POST」 和「ERROR」,整個生命週期能夠用下圖來表示後端
Zuul 大部分功能都是經過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。api
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理 Servlet 的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝 HttpServletRequest 請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId 請求轉發 |
route | 100 | SimpleHostRoutingFilter | url 請求轉發 |
route | 500 | SendForwardFilter | forward 請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
能夠在 application.yml 中配置須要禁用的 filter,格式爲zuul.<SimpleClassName>.<filterType>.disable=true
。
好比要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
就設置安全
zuul: SendResponseFilter: post: disable: true
首先自定義一個 Filter,繼承 ZuulFilter 抽象類,在 run() 方法中添加具體業務邏輯,具體以下:併發
1 package com.carry.springcloud; 2 3 import javax.servlet.http.HttpServletRequest; 4 5 import com.netflix.zuul.ZuulFilter; 6 import com.netflix.zuul.context.RequestContext; 7 8 public class MyFilter extends ZuulFilter { 9 10 /** 11 * 過濾器的類型,它決定過濾器在請求的哪一個生命週期中執行。 這裏定義爲pre,表明會在請求被路由以前執行。 12 * 13 * @return 14 */ 15 @Override 16 public String filterType() { 17 return "pre"; 18 } 19 20 /** 21 * filter執行順序,經過數字指定。 數字越大,優先級越低。 22 * 23 * @return 24 */ 25 @Override 26 public int filterOrder() { 27 return 0; 28 } 29 30 /** 31 * 判斷該過濾器是否須要被執行。這裏咱們直接返回了true,所以該過濾器對全部請求都會生效。 實際運用中咱們能夠利用該函數來指定過濾器的有效範圍。 32 * 33 * @return 34 */ 35 @Override 36 public boolean shouldFilter() { 37 return true; 38 } 39 40 /** 41 * 過濾器的具體邏輯 42 * 43 * @return 44 */ 45 @Override 46 public Object run() { 47 RequestContext ctx = RequestContext.getCurrentContext(); 48 HttpServletRequest request = ctx.getRequest(); 49 50 String token = request.getParameter("token"); 51 if (token == null || token.isEmpty()) { 52 ctx.setSendZuulResponse(false); 53 ctx.setResponseStatusCode(401); 54 ctx.setResponseBody("token is empty"); 55 } 56 return null; 57 } 58 }
在上面實現的過濾器代碼中,咱們經過繼承ZuulFilter
抽象類並重寫了下面的四個方法來實現自定義的過濾器。這四個方法分別定義了:app
filterType()
:過濾器的類型,它決定過濾器在請求的哪一個生命週期中執行。這裏定義爲pre
,表明會在請求被路由以前執行。filterOrder()
:過濾器的執行順序。當請求在一個階段中存在多個過濾器時,須要根據該方法返回的值來依次執行。經過數字指定,數字越大,優先級越低。shouldFilter()
:判斷該過濾器是否須要被執行。這裏咱們直接返回了true
,所以該過濾器對全部請求都會生效。實際運用中咱們能夠利用該函數來指定過濾器的有效範圍。run()
:過濾器的具體邏輯。這裏咱們經過ctx.setSendZuulResponse(false)
令 Zuul 過濾該請求,不對其進行路由,而後經過ctx.setResponseStatusCode(401)
設置了其返回的錯誤碼,固然咱們也能夠進一步優化咱們的返回,好比,經過ctx.setResponseBody(body)
對返回 body 內容進行編輯等。在實現了自定義過濾器以後,它並不會直接生效,咱們還須要爲其建立具體的 Bean 才能啓動該過濾器,好比,在應用主類中增長以下內容:ide
1 package com.carry.springcloud; 2 3 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.autoconfigure.SpringBootApplication; 5 import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 6 import org.springframework.context.annotation.Bean; 7 8 @EnableZuulProxy 9 @SpringBootApplication 10 public class ServiceApiGatewayApplication { 11 12 public static void main(String[] args) { 13 SpringApplication.run(ServiceApiGatewayApplication.class, args); 14 } 15 16 @Bean 17 public MyFilter myFilter() { 18 return new MyFilter(); 19 } 20 }
從新啓動service-api-gateway,併發起下面的請求,對上面定義的過濾器作一個驗證:函數
token is empty
/getPoducerInfo
接口