Spring Cloud Zuul 過濾器

Spring Cloud Zuul 除了能夠實現請求的路由功能,還有一個重要的功能就是過濾器。Zuul 的路由功能讓全部的微服務提供的接口有統一的網關入口,但並非全部的接口都是對外徹底開發的,它們的訪問權限通常都有必定的限制。那咱們能夠在每一個服務都加上對應的校驗和權限鑑定,那這些一般都是用過濾器或攔截器實現的,並且一個系統的各個服務的校驗也大都是類似,這些類似的校驗邏輯打碼在每一個服務都會有一份,不只冗餘且維護麻煩。更好的辦法就是在請求的最前端統一去作這樣的事情,而統一的 API 服務網關入口就是合適的選擇前端

Zuul 能夠經過定義過濾器來實現請求的攔截和過濾,而它自己的大部分功能也是經過過濾器實現的java

過濾器

在 Zuul 中自定義過濾器須要繼承抽象類 ZuulFilter ,須要實現如下4個方法:git

String filterType(); // 過濾器類型

int filterOrder(); // 執行順序,數值越小優先級越高
                      
boolean shouldFilter(); // 執行過濾器的條件

Object run() throws ZuulException; // 具體的過濾操做

從名字咱們也能知道各個方法的做用,下面來了解下過濾器類型和過濾器的生命週期,以及自定義過濾器的使用github

過濾器類型和生命週期

Zuul 定義了4種不一樣的過濾器類型,對應着請求的典型生命週期web

  • PRE: 在請求被路由以前執行。能夠用於請求身份驗證、選擇源服務器和記錄調試信息
  • ROURING: 該過濾器將請求路由到服務。用於構建和發送給微服務的請求(使用 Apache HttpClient 或 Netflix Ribbon 請求服務)
  • POST: 在請求被路由到服務以後執行。能夠用於向響應添加標準的 HTTP Header、收集統計數據和指標,以及將響應從源服務發送到客戶端
  • ERROR: 該過濾器在其餘階段發生錯誤時執行

除了默認的過濾器流,Zuul 還容許咱們建立自定義過濾器類型並顯式地執行它們。例如,咱們能夠自定義一個 STATIC 類型的過濾器,它在Zuul 中生成響應,而不是將請求轉發到後端的服務spring

下面是 Zuul 的生命週期圖,描述着各類類型的過濾器的執行順序(圖片來源於 Zuul Wikijson

Zuul 的生命週期圖

Spring Cloud Zuul 的過濾器

Spring Cloud Zuul 做爲服務網關的大部分功能都是經過過濾器實現的,它在請求的各個階段實現了一系列的過濾器,在 Spring Cloud Zuul 網關服務啓動時自動加載和啓用。實現的這些過濾器是在 spring-cloud-netflix-zuul 模塊中的 org.springframework.cloud.netflix.zuul.filters 包下面後端

下面介紹部分過濾器的功能服務器

Pre filtersapp

filter order 說明
ServletDetectionFilter -3 檢測請求是否經過 Spring 調度程序,即判斷請求是交由 Spring DispatcherServlet 處理,仍是 ZuulServlet 處理(主要是用於大文件上傳)
Servlet30WrapperFilter -2 把原始 HttpServletRequest 包裝成 Servlet30RequestWrapper 對象
FormBodyWrapperFilter -1 解析表單數據併爲下游服務從新編碼
DebugFilter 1 若是設置 debug 請求參數,則此過濾器將RequestContext.setDebugRouting() 和 RequestContext.setDebugRequest() 設置爲true
PreDecorationFilter 5 根據提供的 RouteLocator 肯定路由的位置和方式,它還爲下游請求設置各類與代理相關的頭文件

Route filters

filter order 說明
RibbonRoutingFilter 10 使用 Ribbon、Hystrix 和 可插拔 HTTP客戶機發送請求。只對 RequestContext 存在 serviceId 參數的請求進行處理,即只對經過 serviceId配置路由規則的請求路由。可使用不一樣的 HTTP 客戶端:HttpClient、OkHttpClient、Netflix Ribbon HTTP client
SimpleHostRoutingFilter 100 經過 Apache HttpClient 發送請求到預約的 url,這些 url 能夠在 RequestContext.getRouteHost() 中找到,即只對經過 url 配置路由規則的請求路由
SendForwardFilter 500 經過使用 Servlet RequestDispatcher 轉發請求。用於轉發請求到當前應用的端點

Post filters

filter order 說明
LocationRewriteFilter 900 負責將 Location header 重寫爲 Zuul URL
SendResponseFilter 1000 將代理請求的響應寫入當前響應

Error filters

filter order 說明
SendErrorFilter 0 利用請求上下文中的錯誤信息來組織成一個 forward 到 /error 錯誤端點的請求來產生錯誤響應

禁用過濾器

默認狀況下,這些過濾器在代理和服務器模式下都是啓用的。若是在某些場景下,禁用某個過濾器,能夠設置 zuul.<SimpleClassName>.<filterType>.disable=true。例如,要禁用org.springframework.cloud.netflix.zuul.filter.post.sendresponsefilter,設置 zuul.SendResponseFilter.post.disable=true

自定義過濾器

建立一個 Spring Boot 項目 zuul-filters,Zuul 的服務網關路由配置能夠見 Spring Cloud Zuul 構建微服務網關

spring:
  application:
    name: zuul-filters
server:
  port: 8090
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

zuul:
  ignoredServices: '*'
  routes:
    product:
      path: /product/**
      serviceId: product-service

management:
  endpoints:
    web:
      exposure:
        include: '*'

自定義一個過濾器 AccessFilter,對請求中沒有 accessToken 參數的請求,返回 401拒絕訪問

@Log4j2
public class AccessFilter extends ZuulFilter {

    @Override
    public int filterOrder() {
        // run before PreDecoration
        return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        // a filter has already forwarded
        // a filter has already determined serviceId
        return !ctx.containsKey(FilterConstants.FORWARD_TO_KEY)
                && !ctx.containsKey(FilterConstants.SERVICE_ID_KEY);
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("accessToken");
        if(StringUtils.isBlank(token)) {
            log.warn("access token is empty");
            // 過濾該請求,不對其進行路由
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            return null;
        }
        log.info("access token ok");
        return null;
    }
}

實現自定義過濾器後,把它添加到 Spring 的 Beans 中

@SpringBootApplication
@EnableZuulProxy
public class ZuulFiltersApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulFiltersApplication.class, args);
    }

    @Bean
    public AccessFilter accessFilter() {
        return new AccessFilter();
    }
}

下面咱們來測試下結果,啓動項目 eureka-serverproduct-serivce(做爲代理的服務)、zuul-filters

訪問 http://localhost:8090/product/product/1 返回 401
訪問 http://localhost:8090/product/product/1?accessToken=111 會正常路由到 product-service 的 /product/1

參考代碼見:demo

過濾器管理端點

@EnableZuulProxy 註解配合 Spring Boot Actuator,Zuul 會暴露額外的兩個管理端點:RoutesFilters。分別是關於路由和過濾器的端點(服務路由的端點在這裏介紹 Spring Cloud Zuul 構建微服務網關

spring-cloud-starter-netflix-zuul 已經依賴了 spring-boot-starter-actuator,因此上面的工程已經包含了路由管理的功能。關於過濾器的管理端點的路徑爲 /filters

訪問路徑 http://localhost:8090/actuator/filters

{
  "error": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter",
      "order": 0,
      "disabled": false,
      "static": true
    }
  ],
  "post": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
      "order": 1000,
      "disabled": false,
      "static": true
    }
  ],
  "pre": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter",
      "order": 1,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter",
      "order": -1,
      "disabled": false,
      "static": true
    },
    {
      "class": "com.turbosnail.zuul.filter.AccessFilter",
      "order": 4,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter",
      "order": -2,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter",
      "order": -3,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter",
      "order": 5,
      "disabled": false,
      "static": true
    }
  ],
  "route": [
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter",
      "order": 100,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
      "order": 10,
      "disabled": false,
      "static": true
    },
    {
      "class": "org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter",
      "order": 500,
      "disabled": false,
      "static": true
    }
  ]
}

訪問404是由於沒有暴露端點,能夠設置 management.endpoints.web.exposure.include: '*'

相關文章
相關標籤/搜索