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}

  

  訪問://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

  

  訪問://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

  

  訪問://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}

  

  訪問://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);
    }

}

  

  訪問://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

  

  訪問://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();
    }

}

  

訪問

  

  注釋配置文件中所有網關配置,重啟並訪問://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;
    }

}

  

訪問

  

  訪問://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;
    }

}

  

訪問

  

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

  

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

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

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

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

  

  🤗 您的點贊轉發是對我最大的支援。

  📢 掃碼關注 哈嘍沃德先生「文檔 + 影片」每篇文章都配有專門影片講解,學習更輕鬆噢 ~