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ù)邏輯的處理。