經過服務網關統一貫外系統提供REST API的過程當中,除了具有服務路由、均衡負載功能以外,它還具有了權限控制等功能。Spring Cloud Netflix中的Zuul就擔任了這樣的一個角色,爲微服務架構提供了前門保護的做用,同時將權限控制這些較重的非業務邏輯內容遷移到服務路由層面,使得服務集羣主體可以具有更高的可複用性和可測試性。html
下面咱們經過實例例子來使用一下Zuul來做爲服務的路有功能。java
在使用Zuul以前,咱們先構建一個服務註冊中心、以及兩個簡單的服務,好比:我構建了一個service-A,一個service-B。而後啓動eureka-server和這兩個服務。經過訪問eureka-server,咱們能夠看到service-A和service-B已經註冊到了服務中心。git
若是您還不熟悉如何構建服務中心和註冊服務,請先閱讀1--SpringCloud的服務註冊與發現Eurekaweb
首先建立一個簡單的spring-boot項目zuul-getwayspring
pom文件以下:api
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 網關依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
應用主類使用@EnableZuulProxy
註解開啓Zuul安全
@EnableZuulProxy @SpringCloudApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } @Bean public AccessFilter accessFilter() { return new AccessFilter(); } }
這裏用了@SpringCloudApplication
註解,以前沒有提過,經過源碼咱們看到,它整合了@SpringBootApplication
、@EnableDiscoveryClient
、@EnableCircuitBreaker
,主要目的仍是簡化配置。架構
application.properties 配置文件app
spring.application.name=api-gateway
server.port=5555
經過服務路由的功能,咱們在對外提供服務的時候,只須要經過暴露Zuul中配置的調用地址就可讓調用方統一的來訪問咱們的服務,而不須要了解具體提供服務的主機信息了。ide
在Zuul中提供了兩種映射方式:
zuul.routes.api-a-url.path=/api-a-url/** zuul.routes.api-a-url.url=http://localhost:2222/
該配置,定義了,全部到Zuul的中規則爲:/api-a-url/**
的訪問都映射到http://localhost:2222/
上,也就是說當咱們訪問http://localhost:4444/api-a-url/add?a=1&b=2
的時候,Zuul會將該請求路由到:http://localhost:2222/add?a=1&b=2
上。
其中,配置屬性zuul.routes.api-a-url.path中的api-a-url部分爲路由的名字,能夠任意定義,可是一組映射關係的path和url要相同,下面講serviceId時候也是如此。
經過url映射的方式對於Zuul來講,並非特別友好,Zuul須要知道咱們全部爲服務的地址,才能完成全部的映射配置。而實際上,咱們在實現微服務架構時,服務名與服務實例地址的關係在eureka server中已經存在了,因此只須要將Zuul註冊到eureka server上去發現其餘服務,咱們就能夠實現對serviceId的映射。例如,咱們能夠以下配置:完整application.properties 配置文件
spring.application.name=api-gateway #啓動端口 server.port=4444 #服務中心地址 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=service-A zuul.routes.api-b.path=/api-b/** zuul.routes.api-b.serviceId=service-B
針對咱們在準備工做中實現的兩個微服務service-A和service-B,定義了兩個路由api-a和api-b來分別映射。另外爲了讓Zuul能發現service-A和service-B,也加入了eureka的配置。
接下來,咱們將eureka-server、service-A、service-B以及這裏用Zuul實現的服務網關啓動起來,在eureka-server的控制頁面中,咱們能夠看到分別註冊了service-A、service-B以及api-gateway
嘗試經過服務網關來訪問service-A和service-B,根據配置的映射關係,分別訪問下面的url
http://localhost:4444/api-a/add?a=1&b=2
:經過serviceId映射訪問service-A中的add服務http://localhost:4444/api-b/add?a=1&b=2
:經過serviceId映射訪問service-B中的add服務http://localhost:4444/api-a-url/add?a=1&b=2
:經過url映射訪問service-A中的add服務(本示例並無使用url映射,能夠在配置文件裏面去掉serviceId映射配置,添加上url映射)推薦使用serviceId的映射方式,除了對Zuul維護上更加友好以外,serviceId映射方式還支持了斷路器,對於服務故障的狀況下,能夠有效的防止故障蔓延到服務網關上而影響整個系統的對外服務
在完成了服務路由以後,咱們對外開放服務還須要一些安全措施來保護客戶端只能訪問它應該訪問到的資源。因此咱們須要利用Zuul的過濾器來實現咱們對外服務的安全控制。
在服務網關中定義過濾器只須要繼承ZuulFilter
抽象類實現其定義的四個抽象函數就可對請求進行攔截與過濾。
好比下面的例子,定義了一個Zuul過濾器,實現了在請求被路由以前檢查請求中是否有accessToken
參數,如有就進行路由,若沒有就拒絕訪問,返回401 Unauthorized
錯誤。
package com; import javax.servlet.http.HttpServletRequest; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; public class AccessFilter extends ZuulFilter{ public Object run() {//過濾器的具體邏輯 // TODO Auto-generated method stub RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object accessToken = request.getParameter("accessToken"); if(accessToken == null) { ctx.setSendZuulResponse(false);//不進行路由 ctx.setResponseStatusCode(401); return null; } return null; } public boolean shouldFilter() {//返回一個boolean類型來判斷該過濾器是否要執行,因此經過此函數可實現過濾器的開關 // TODO Auto-generated method stub return true;//true 總的是執行 } @Override public int filterOrder() {//過濾的執行順序 // TODO Auto-generated method stub return 0; } /* (non-Javadoc) * @see com.netflix.zuul.ZuulFilter#filterType() * pre:能夠在請求被路由以前調用 routing:在路由請求時候被調用 post:在routing和error過濾器以後被調用 error:處理請求時發生錯誤時被調用 */ @Override public String filterType() {//過濾器的執行時間 // TODO Auto-generated method stub return "pre"; } }
自定義過濾器的實現,須要繼承ZuulFilter
,須要重寫實現下面四個方法:
filterType
:返回一個字符串表明過濾器的類型,在zuul中定義了四種不一樣生命週期的過濾器類型,具體以下:
pre
:能夠在請求被路由以前調用routing
:在路由請求時候被調用post
:在routing和error過濾器以後被調用error
:處理請求時發生錯誤時被調用filterOrder
:經過int值來定義過濾器的執行順序shouldFilter
:返回一個boolean類型來判斷該過濾器是否要執行,因此經過此函數可實現過濾器的開關。在上例中,咱們直接返回true,因此該過濾器老是生效。run
:過濾器的具體邏輯。須要注意,這裏咱們經過ctx.setSendZuulResponse(false)
令zuul過濾該請求,不對其進行路由,而後經過ctx.setResponseStatusCode(401)
設置了其返回的錯誤碼,固然咱們也能夠進一步優化咱們的返回,好比,經過ctx.setResponseBody(body)
對返回body內容進行編輯等。http://localhost:4444/api-a/add?a=1&b=2 沒有進行路由
http://localhost:4444/api-a/add?a=1&b=2&accessToken 進行路由
獲取源碼:3--SpringCloud網關zuul