Spring Cloud:第五章:Zuul服務(wù)網(wǎng)關(guān)
快速入門
定義user,order,pay服務(wù),定義zull服務(wù)網(wǎng)關(guān)服務(wù)都注冊到eureka服務(wù)上,
通過一下接口訪問user,order,pay的服務(wù),
http://localhost:7070/pay/index
http://localhost:8080/user/index
http://localhost:9090/order/index
定義服務(wù)網(wǎng)關(guān)服務(wù)zuul,我們看看其相關(guān)配置,zuul-service加入依賴:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
配置文件:
spring:
application:
name: zuul-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
prefer-ip-address: true
server:
port: 6069
定義啟動類:
package com.zhihao.miao;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
//使用@EnableZuulProxy注解開啟zuul的api網(wǎng)關(guān)服務(wù)功能
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
然后可以通過服務(wù)網(wǎng)關(guān)進行訪問上面的三個服務(wù)接口,
http://localhost:6069/pay-service/pay/index
http://localhost:6069/user-service/user/index
http://localhost:6069/order-service/order/index
注意:
默認(rèn)的zuul結(jié)合eureka會將注冊到eureka的服務(wù)名作為訪問的ContextPath。
也可以指定一些路由機制,application.yml中定義如下,
zuul:
routes:
user-service:
path: /users/**
serviceId: user-service
url符合/users/**規(guī)則的就被轉(zhuǎn)發(fā)到user-service實例中了。
http://localhost:6069/users/user/index
之前的通過服務(wù)實例名稱的也能訪問
http://localhost:6069/user-service/user/index
也可以忽略所有的代理,只保留自己配置的即可。
zuul:
ignored-services: '*'
routes:
user-service: /users/**
此時只能訪問user-service服務(wù),并且只能以users這種路由來訪問。
不能訪問
http://localhost:6069/pay-service/pay/index
http://localhost:6069/order-service/order/index
之前默認(rèn)的注冊到eureka上的服務(wù)(user-service)也不能訪問。
不能訪問
http://192.168.1.57:6069/user-service/user/index
可以訪問
http://192.168.1.57:6069/users/user/index
以上的配置也可以這樣指定
zuul:
ignored-services: order-service,pay-service
routes:
user-service: /users/**
當(dāng)然此時除了http://192.168.1.57:6069/users/user/index能被代理訪問到
http://192.168.1.57:6069/user-service/user/index也能訪問了
請求過濾
在實現(xiàn)請求理由功能之后,我們的微服務(wù)應(yīng)用提供的接口就可以通過統(tǒng)一的api網(wǎng)關(guān)入口被客戶端訪問到了。每個客戶端用戶請求服務(wù)應(yīng)用提供的接口時,他們的訪問權(quán)限往往都有一定的限制,系統(tǒng)并不會將所有的微服務(wù)接口對他們開放。然而,目前的服務(wù)路由并沒有限制權(quán)限這樣的功能,所有請求都會被毫無保留的轉(zhuǎn)發(fā)到具體的應(yīng)用并返回結(jié)果,為了實現(xiàn)對客戶端請求的安全校驗和權(quán)限控制,最簡單和粗暴的方法就是在每個微服務(wù)應(yīng)用都實現(xiàn)一套用于校驗簽名和鑒別權(quán)限的過濾器或攔截器。這樣有個問題就是功能實現(xiàn)太過冗余。比較好的做法就是將這些校驗邏輯剝離出去,構(gòu)建一個獨立的鑒權(quán)服務(wù)。在完成剝離之后,直接在微服務(wù)應(yīng)用中通過調(diào)用鑒權(quán)系統(tǒng)服務(wù)來實現(xiàn)校驗,但是這樣僅僅只是解決了鑒權(quán)邏輯的分離,并沒有在本質(zhì)上將這部分不屬于冗余的邏輯從原有的微服務(wù)應(yīng)用中拆出去,冗余的攔截器或者過濾器依然會存在。
對于這樣的問題,更好的做法是通過前置的網(wǎng)關(guān)服務(wù)來完成這些非業(yè)務(wù)性質(zhì)的校驗。由于網(wǎng)關(guān)服務(wù)的加入,外部客戶端訪問我們的系統(tǒng)已經(jīng)有了統(tǒng)一的入口,既然這些校驗與具體的業(yè)務(wù)無關(guān),那何不在請求到達的時候就完成校驗和過濾,微服務(wù)應(yīng)用端就可以去除各種復(fù)雜的過濾器和攔截器了,這使得微服務(wù)應(yīng)用接口的開發(fā)和測試復(fù)雜度也得到了相應(yīng)的降低。這就涉及到了zuul的另一個主要功能,請求過濾。
在zuul-service服務(wù)中定義一個過濾器,
public class AccessFilter extends ZuulFilter{
private Logger logger = LoggerFactory.getLogger(getClass());
//過濾器的類型,它決定過濾器在請求的哪個生命周期中執(zhí)行,這里定義為pre,代表會在請求被理由之前執(zhí)行。
@Override
public String filterType() {
return "pre";
}
//過濾器的執(zhí)行順序。當(dāng)請求在一個階段中存在多個過濾器時,需要根據(jù)該方法返回的值來依次執(zhí)行
@Override
public int filterOrder() {
return 0;
}
//判斷該過濾器是否需要被執(zhí)行。這里我們直接返回了true,因此該過濾器對所有的請求都生效。實際運行中我們可以利用該函數(shù)
//來指定過濾器的有效范圍。
@Override
public boolean shouldFilter() {
return true;
}
//過濾器的具體執(zhí)行邏輯。
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("send {} request to {}",request.getMethod(),request.getRequestURI().toString());
Object accessToken = request.getParameter("accessToken");
if(accessToken == null){
logger.warn("access token is empty");
ctx.setSendZuulResponse(false); //令zuul過濾該請求,不對其進行路由
ctx.setResponseStatusCode(401); //設(shè)置返回的錯誤碼
}
logger.info("access token ok");
return null;
}
}
ZuulFilter接口中定義四個方法:
filterType:過濾器的類型,它決定過濾器在請求的哪個生命周期中執(zhí)行,這里定義為pre,代表會在請求被理由之前執(zhí)行。
filterOrder:過濾器的執(zhí)行順序。當(dāng)請求在一個階段中存在多個過濾器時,需要根據(jù)該方法返回的值來依次執(zhí)行。
shouldFilter:判斷該過濾器是否需要被執(zhí)行。這里我們直接返回了true,因此該過濾器對所有的請求都生效。實際運行中我們可以利用該函數(shù)。
run:過濾器的具體執(zhí)行邏輯。
在將過濾器納入spring容器中,
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
@Bean
public AccessFilter accessFilter(){
return new AccessFilter();
}
}
訪問服務(wù):
http://localhost:6069/users/user/index
狀態(tài)碼錯誤為401,正確訪問姿勢是
http://localhost:6069/users/user/index?accessToken=111
過濾器類型與請求生命周期
PRE:這種過濾器在請求被路由之前調(diào)用。我們可利用這種過濾器實現(xiàn)身份驗證、在集群中選擇請求的微服務(wù)、記錄調(diào)試信息等。
ROUTING:這種過濾器將請求路由到微服務(wù)。這種過濾器用于構(gòu)建發(fā)送給微服務(wù)的請求,并使用Apache HttpClient或Netfilx Ribbon請求微服務(wù)。
POST:這種過濾器在路由到微服務(wù)以后執(zhí)行。這種過濾器可用來為響應(yīng)添加標(biāo)準(zhǔn)的HTTP Header、收集統(tǒng)計信息和指標(biāo)、將響應(yīng)從微服務(wù)發(fā)送給客戶端等。
ERROR:在其他階段發(fā)生錯誤時執(zhí)行該過濾器。
除了默認(rèn)的過濾器類型,Zuul還允許我們創(chuàng)建自定義的過濾器類型。例如,我們可以定制一種STATIC類型的過濾器,直接在Zuul中生成響應(yīng),而不將請求轉(zhuǎn)發(fā)到后端的微服務(wù)。
總結(jié)
就目前掌握的api網(wǎng)關(guān)知識,總結(jié)出四點如下
它作為系統(tǒng)的統(tǒng)一入口,屏蔽了系統(tǒng)內(nèi)部各個微服務(wù)的細(xì)節(jié)。
它可以與服務(wù)治理框架結(jié)合,實現(xiàn)自動化的服務(wù)實例維護以及負(fù)載均衡的路由轉(zhuǎn)發(fā)。
它可以實現(xiàn)接口權(quán)限校驗與微服務(wù)業(yè)務(wù)邏輯的解耦。
通過服務(wù)網(wǎng)關(guān)中的過濾器,在各個生命周期中去校驗請求的內(nèi)容,將原本在對外服務(wù)層做的校驗遷移,保證了微服務(wù)的無狀態(tài)性,同時降低了微服務(wù)的測試難度,讓服務(wù)本身更集中關(guān)注業(yè)務(wù)邏輯的處理。