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 的文章。


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

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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章