1. 程式人生 > >Spring Cloud 系列之 Gateway 服務閘道器(三)

Spring Cloud 系列之 Gateway 服務閘道器(三)

本篇文章為系列文章,未讀第一集的同學請猛戳這裡:

  • Spring Cloud 系列之 Gateway 服務閘道器(一)
  • Spring Cloud 系列之 Gateway 服務閘道器(二)

本篇文章講解 Gateway 閘道器過濾器和全域性過濾器以及自定義過濾器。

  

過濾器

  

  Spring Cloud Gateway 根據作用範圍劃分為 GatewayFilterGlobalFilter,二者區別如下:

  • GatewayFilter:閘道器過濾器,需要通過 spring.cloud.routes.filters 配置在具體路由下,只作用在當前路由上或通過 spring.cloud.default-filters
    配置在全域性,作用在所有路由上。
  • GlobalFilter:全域性過濾器,不需要在配置檔案中配置,作用在所有的路由上,最終通過 GatewayFilterAdapter 包裝成 GatewayFilterChain 可識別的過濾器,它為請求業務以及路由的 URI 轉換為真實業務服務請求地址的核心過濾器,不需要配置系統初始化時載入,並作用在每個路由上。

  

閘道器過濾器 GatewayFilter

  

  點選連結觀看:閘道器過濾器視訊(獲取更多請關注公眾號「哈嘍沃德先生」)

  

  閘道器過濾器用於攔截並鏈式處理 Web 請求,可以實現橫切與應用無關的需求,比如:安全、訪問超時的設定等。修改傳入的 HTTP 請求或傳出 HTTP 響應。Spring Cloud Gateway 包含許多內建的閘道器過濾器工廠一共有 22 個,包括頭部過濾器、 路徑類過濾器、Hystrix 過濾器和重寫請求 URL 的過濾器, 還有引數和狀態碼等其他型別的過濾器。根據過濾器工廠的用途來劃分,可以分為以下幾種:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。

  

  接下來我們舉例說明其中一部分如何使用,其餘等大家工作中需要應用時再查詢資料學習或者諮詢我也可以。

  

Path 路徑過濾器

  

  Path 路徑過濾器可以實現 URL 重寫,通過重寫 URL 可以實現隱藏實際路徑提高安全性,易於使用者記憶和鍵入,易於被搜尋引擎收錄等優點。實現方式如下:

  

RewritePathGatewayFilterFactory

  

  RewritePath 閘道器過濾器工廠採用路徑正則表示式引數和替換引數,使用 Java 正則表示式來靈活地重寫請求路徑。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/product/**, /api-gateway/**
          filters:                      # 閘道器過濾器
            # 將 /api-gateway/product/1 重寫為 /product/1
            - RewritePath=/api-gateway(?<segment>/?.*), $\{segment}

  

  訪問:http://localhost:9000/api-gateway/product/1 結果如下:

  

PrefixPathGatewayFilterFactory

  

  PrefixPath 閘道器過濾器工廠為匹配的 URI 新增指定字首。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/**
          filters:                       # 閘道器過濾器
            # 將 /1 重寫為 /product/1
            - PrefixPath=/product

  

  訪問:http://localhost:9000/1 結果如下:

  

StripPrefixGatewayFilterFactory

  

  StripPrefix 閘道器過濾器工廠採用一個引數 StripPrefix,該引數表示在將請求傳送到下游之前從請求中剝離的路徑個數。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/**
          filters:                       # 閘道器過濾器
            # 將 /api/123/product/1 重寫為 /product/1
            - StripPrefix=2

  

  訪問:http://localhost:9000/api/123/product/1 結果如下:

  

SetPathGatewayFilterFactory

  

  SetPath 閘道器過濾器工廠採用路徑模板引數。 它提供了一種通過允許模板化路徑段來操作請求路徑的簡單方法,使用了 Spring Framework 中的 uri 模板,允許多個匹配段。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/api/product/{segment}
          filters:                       # 閘道器過濾器
            # 將 /api/product/1 重寫為 /product/1
            - SetPath=/product/{segment}

  

  訪問:http://localhost:9000/api/product/1 結果如下:

  

Parameter 引數過濾器

  

  AddRequestParameter 閘道器過濾器工廠會將指定引數新增至匹配到的下游請求中。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/api-gateway/**
          filters:                       # 閘道器過濾器
            # 將 /api-gateway/product/1 重寫為 /product/1
            - RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
            # 在下游請求中新增 flag=1
            - AddRequestParameter=flag, 1

  

  修改商品服務的控制層程式碼。

package com.example.controller;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 根據主鍵查詢商品
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Product selectProductById(@PathVariable("id") Integer id, String flag) {
        System.out.println("flag = " + flag);
        return productService.selectProductById(id);
    }

}

  

  訪問:http://localhost:9000/api-gateway/product/1 控制檯結果如下:

flag = 1

  

Status 狀態過濾器

  

  SetStatus 閘道器過濾器工廠採用單個狀態引數,它必須是有效的 Spring HttpStatus。它可以是整數 404 或列舉 NOT_FOUND 的字串表示。

spring:
  application:
    name: gateway-server # 應用名稱
  cloud:
    gateway:
      # 路由規則
      routes:
        - id: product-service           # 路由 ID,唯一
          uri: lb://product-service     # lb:// 根據服務名稱從註冊中心獲取服務請求地址
          predicates:                   # 斷言(判斷條件)
            # 匹配對應 URI 的請求,將匹配到的請求追加在目標 URI 之後
            - Path=/api-gateway/**
          filters:                       # 閘道器過濾器
            # 將 /api-gateway/product/1 重寫為 /product/1
            - RewritePath=/api-gateway(?<segment>/?.*), $\{segment}
            # 任何情況下,響應的 HTTP 狀態都將設定為 404
            - SetStatus=404 			 # 404 或者對應的列舉 NOT_FOUND

  

  訪問:http://localhost:9000/api-gateway/product/1 結果如下:

  

全域性過濾器 GlobalFilter

  

  全域性過濾器不需要在配置檔案中配置,作用在所有的路由上,最終通過 GatewayFilterAdapter 包裝成 GatewayFilterChain 可識別的過濾器,它是請求業務以及路由的 URI 轉換為真實業務服務請求地址的核心過濾器,不需要配置系統初始化時載入,並作用在每個路由上。

  

自定義過濾器

  

  即使 Spring Cloud Gateway 自帶許多實用的 GatewayFilter Factory、Gateway Filter、Global Filter,但是在很多情景下我們仍然希望可以自定義自己的過濾器,實現一些騷操作。

  

自定義閘道器過濾器

  

  自定義閘道器過濾器需要實現以下兩個介面 :GatewayFilterOrdered

  

建立過濾器

  

  CustomGatewayFilter.java

package com.example.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定義閘道器過濾器
 */
public class CustomGatewayFilter implements GatewayFilter, Ordered {

    /**
     * 過濾器業務邏輯
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("自定義閘道器過濾器被執行");
        return chain.filter(exchange); // 繼續向下執行
    }

    /**
     * 過濾器執行順序,數值越小,優先順序越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

}

  

註冊過濾器

  

package com.example.config;

import com.example.filter.CustomGatewayFilter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 閘道器路由配置類
 */
@Configuration
public class GatewayRoutesConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes().route(r -> r
                // 斷言(判斷條件)
                .path("/product/**")
                // 目標 URI,路由到微服務的地址
                .uri("lb://product-service")
                // 註冊自定義閘道器過濾器
                .filters(new CustomGatewayFilter())
                // 路由 ID,唯一
                .id("product-service"))
                .build();
    }

}

  

訪問

  

  註釋配置檔案中所有閘道器配置,重啟並訪問:http://localhost:9000/product/1 控制檯結果如下:

自定義閘道器過濾器被執行

  

自定義全域性過濾器

  

  自定義全域性過濾器需要實現以下兩個介面 :GlobalFilterOrdered。通過全域性過濾器可以實現許可權校驗,安全性驗證等功能。

  

建立過濾器

  

  實現指定介面,新增 @Component 註解即可。

  CustomGlobalFilter.java

package com.example.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定義全域性過濾器
 */
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {

    /**
     * 過濾器業務邏輯
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("自定義全域性過濾器被執行");
        return chain.filter(exchange); // 繼續向下執行
    }

    /**
     * 過濾器執行順序,數值越小,優先順序越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }

}

  

訪問

  

  訪問:http://localhost:9000/product/1 控制檯結果如下:

自定義全域性過濾器被執行

  

統一鑑權

  

  接下來我們在閘道器過濾器中通過 token 判斷使用者是否登入,完成一個統一鑑權案例。

  

建立過濾器

  

  AccessFilter.java

package com.example.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 許可權驗證過濾器
 */
@Component
public class AccessFilter implements GlobalFilter, Ordered {

    private Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    /**
     * 過濾器業務邏輯
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 獲取請求引數
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        // 業務邏輯處理
        if (null == token) {
            logger.warn("token is null...");
            ServerHttpResponse response = exchange.getResponse();
            // 響應型別
            response.getHeaders().add("Content-Type", "application/json; charset=utf-8");
            // 響應狀態碼,HTTP 401 錯誤代表使用者沒有訪問許可權
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 響應內容
            String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}";
            DataBuffer buffer = response.bufferFactory().wrap(message.getBytes());
            // 請求結束,不在繼續向下請求
            return response.writeWith(Mono.just(buffer));
        }
        // 使用 token 進行身份驗證
        logger.info("token is OK!");
        return chain.filter(exchange);
    }

    /**
     * 過濾器執行順序,數值越小,優先順序越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }

}

  

訪問

  

  訪問:http://localhost:9000/product/1 結果如下:

  

  訪問:http://localhost:9000/product/1?token=abc123 結果如下:

下一篇我們講解 Gateway 閘道器如何實現限流、整合Sentinel實現限流以及高可用閘道器環境搭建,記得關注噢~

  本文采用 知識共享「署名-非商業性使用-禁止演繹 4.0 國際」許可協議

  大家可以通過 分類 檢視更多關於 Spring Cloud 的文章。