1. 程式人生 > 其它 >Spring Cloud Alibaba 整合閘道器 Gateway 全域性過濾功能

Spring Cloud Alibaba 整合閘道器 Gateway 全域性過濾功能

技術標籤:Javaspring cloud alibabacloud gatewayspring boot閘道器全域性過濾

文章目錄


1 摘要

Spring Cloud Gateway 閘道器除了提供路由功能外,也提供了過濾功能。本文將介紹基於 Spring Cloud Alibaba 2.2 整合 Gateway 閘道器實現全域性過濾功能。

關於 Gateway 與 Zuul 區別:

微服務閘道器Zuul和Gateway的區別

Spring Cloud Alibaba 2.2 簡易整合 Gateway:

Spring Cloud Alibaba 整合閘道器Gateway

2 核心 Maven 依賴

./cloud-alibaba-gateway-filter/pom.xml
    <dependencies>
        <!-- cloud alibaba -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <
artifactId
>
spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>${spring-cloud-alibaba.version}</version> </dependency> <!-- cloud feign --> <dependency> <groupId>org.springframework.cloud</groupId
>
<artifactId>spring-cloud-starter-openfeign</artifactId> <version>${spring-cloud-openfeign.version}</version> </dependency> <!-- cloud gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>${spring-cloud-gateway.version}</version> </dependency> <!-- 省略其他依賴 --> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

其中 ${spring-cloud-alibaba.version} 的版本為 2.2.3.RELEASE , ${spring-cloud-gateway.version} 的版本為 2.2.5.RELEASE${spring-cloud-openfeign.version} 的版本為 2.2.5.RELEASE

注意事項: Spring Cloud Alibaba 2.2.3.RELEASE 版本支援的 Spring Boot 版本為 2.3.1.RELEASE ,建議在搭建專案時要保持版本的一致性,Spring Boot 版本過高或過低都可能導致不相容問題

Spring Cloud Gateway 不能和 Spring Boot Web 一起打包

3 核心程式碼

3.1 application 配置

./cloud-alibaba-gateway-filter/src/main/resources/application.yml
## config

## server
server:
  port: 8612

## spring
spring:
  application:
    name: cloud-alibaba-gateway-filter
  cloud:
    nacos:
      discovery:
        server-addr: 172.16.140.10:8688
    gateway:
      discovery:
        locator:
          enabled: true


## endpoint
management:
  endpoints:
    web:
      exposure:
        include: "*"

## log
#logging:
#  level:
#    org.springframework.cloud.gateway: debug

3.2 全域性攔截器-鑑權

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/interceptor/AuthFilter.java
package com.ljq.demo.springboot.alibaba.gateway.filter.interceptor;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ljq.demo.springboot.alibaba.gateway.filter.common.api.ApiResult;
import lombok.extern.slf4j.Slf4j;
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.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * @Description: 鑑權攔截器
 * @Author: junqiang.lu
 * @Date: 2020/12/8
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final String TOKEN_KEY = "token";

    /**
     * 許可權過濾
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        List<String> tokenList = exchange.getRequest().getHeaders().get(TOKEN_KEY);
        log.info("token: {}", tokenList);
        if (CollectionUtils.isEmpty(tokenList) || tokenList.get(0).trim().isEmpty()) {
            ServerHttpResponse response = exchange.getResponse();
            // 錯誤資訊
            byte[] data = new byte[0];
            try {
                data = new ObjectMapper().writeValueAsBytes(ApiResult.fail("Token is null"));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            DataBuffer buffer = response.bufferFactory().wrap(data);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            return response.writeWith(Mono.just(buffer));
        }
        return chain.filter(exchange);
    }

    /**
     * 設定執行級別
     *
     * @return
     */
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

簡要說明:

當通過該閘道器的介面,如果請求頭沒有使用者金鑰(token),就會直接返回異常,不會繼續請求介面

3.3 SpringBoot 啟動類

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/CloudAlibabaGatewayFilterApplication.java
package com.ljq.demo.springboot.alibaba.gateway.filter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @author junqiang.lu
 */
@EnableDiscoveryClient
@SpringBootApplication
public class CloudAlibabaGatewayFilterApplication {

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

}

3.4 其他相關程式碼

返回結果封裝類

./cloud-alibaba-gateway-filter/src/main/java/com/ljq/demo/springboot/alibaba/gateway/filter/common/api/ApiResult.java
package com.ljq.demo.springboot.alibaba.gateway.filter.common.api;

import lombok.Data;

import java.util.Map;

/**
 * @Description: 介面返回結果
 * @Author: junqiang.lu
 * @Date: 2020/9/3
 */
@Data
public class ApiResult<T> {

    /**
     * 預設成功結果 key
     */
    public static final String DEFAULT_SUCCESS_KEY = "api.response.success";
    /**
     * 預設成功返回提示資訊
     */
    public static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
    /**
     * 預設錯誤結果 key
     */
    public static final String DEFAULT_ERROR_KEY = "api.response.error";
    /**
     * 預設錯誤返回提示資訊
     */
    public static final String DEFAULT_ERROR_MESSAGE = "ERROR";

    /**
     * 返回結果 key
     */
    private String key;
    /**
     * 返回提示資訊
     */
    private String message;
    /**
     * 返回資料
     */
    private T data;
    /**
     * 額外返回資料
     */
    private Map<String, Object> extraDataMap;
    /**
     * 系統當前時間(精確到毫秒)
     */
    private Long timestamp = System.currentTimeMillis();

    protected ApiResult(){
        // 保護無參構造方法,儘量避免手動建立物件
    }

    /**
     * 構造方法
     *
     * @param key
     * @param message
     * @param data
     * @param extraDataMap
     */
    protected ApiResult(String key, String message, T data, Map<String, Object> extraDataMap) {
        this.key = key;
        this.message = message;
        this.data = data;
        this.extraDataMap = extraDataMap;
    }

    /**
     * 成功
     *
     * @return
     */
    public static <T> ApiResult<T> success() {
        return success(null,null);
    }

    /**
     * 成功
     *
     * @param data 返回資料
     * @return
     */
    public static <T> ApiResult<T> success(T data) {
        return success(data, null);
    }

    /**
     * 成功
     *
     * @param data 返回資料
     * @param extraDataMap 額外返回資料
     * @return
     */
    public static <T> ApiResult<T> success(T data, Map<String, Object> extraDataMap) {
        return new ApiResult<>(DEFAULT_SUCCESS_KEY, DEFAULT_SUCCESS_MESSAGE, data, extraDataMap);
    }

    /**
     * 失敗
     *
     * @return
     */
    public static <T> ApiResult<T> fail() {
        return fail(DEFAULT_ERROR_KEY, DEFAULT_ERROR_MESSAGE, null, null);
    }

    /**
     * 失敗
     *
     * @param message 返回提示資訊
     * @return
     */
    public static <T> ApiResult<T> fail(String message) {
        return fail(DEFAULT_ERROR_KEY, message, null, null);
    }

    /**
     * 失敗
     *
     * @param responseKey 返回結果 key
     * @param message 返回提示資訊
     * @return
     */
    public static <T> ApiResult<T> fail(String responseKey, String message) {
        return fail(responseKey, message, null, null);
    }

    /**
     * 失敗
     *
     * @param responseKey 返回結果 Key
     * @param message 返回提示資訊
     * @param data 返回資料
     * @param extraDataMap 額外返回資料
     * @return
     */
    public static <T> ApiResult<T> fail(String responseKey, String message, T data, Map<String, Object> extraDataMap) {
        return new ApiResult<>(responseKey, message, data, extraDataMap);
    }


}

至此,一個簡單的鑑權閘道器就實現了。

4 請求測試

啟動閘道器服務(cloud-alibaba-gateway-filter),再啟動一個測試服務(cloud-alibaba-server-provider)

測試請求介面

介面地址與請求引數:

http://127.0.0.1:8612/cloud-alibaba-server-provider/api/nacos/hello?name=%E5%BE%B7%E7%8E%9B%E8%A5%BF%E4%BA%9A-Gateway

請求方式:GET

請求頭(header):

token = eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlblBob25lIjoiMTg2NjY2NjY2NjZAMTU3NTcwNTUyNzgxMiIsImlzcyI6ImF1dGgwIn0.4Ymy0zrAaWimv1uf_hk1pyZzPORp7BP_PC791s9sU6U

返回結果:

Hello,德瑪西亞-Gateway

閘道器服務後臺日誌:

2020-12-18 15:37:05.893  INFO 41025 --- [ctor-http-nio-2] c.l.d.s.a.g.f.interceptor.AuthFilter     : token: [eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlblBob25lIjoiMTg2NjY2NjY2NjZAMTU3NTcwNTUyNzgxMiIsImlzcyI6ImF1dGgwIn0.4Ymy0zrAaWimv1uf_hk1pyZzPORp7BP_PC791s9sU6U]

測試服務後臺日誌:

2020-12-18 15:37:05.903  INFO 41042 --- [nio-8600-exec-3] c.l.d.p.a.s.p.c.NacosProviderController  : serverPort: 8600
2020-12-18 15:37:05.904  INFO 41042 --- [nio-8600-exec-3] c.l.d.p.a.s.p.c.NacosProviderController  : result: Hello,德瑪西亞-Gateway

取消 token 的請求頭之後

返回結果:

{
    "key": "api.response.error",
    "message": "Token is null",
    "data": null,
    "extraDataMap": null,
    "timestamp": 1608277220597
}

閘道器服務後臺日誌:

2020-12-18 15:40:20.597  INFO 41025 --- [ctor-http-nio-2] c.l.d.s.a.g.f.interceptor.AuthFilter     : token: null

測試服務後臺無日誌

從測試結果可以看出在沒有傳 token 的情況下,將無法訪問到目標服務,從而實現了攔截過濾的功能。

至此,Spring Cloud Alibaba 2.2 整合閘道器 Gateway 的全域性攔截功能已經實現了。

5 推薦參考資料

Spring Cloud Alibaba系列教程 - Spring Cloud Alibaba 閘道器全域性過濾

官方文件: Spring Cloud Gateway

Spring Cloud Alibaba 整合閘道器Gateway

6 Github 原始碼

Gtihub 原始碼地址 : https://github.com/Flying9001/springBootDemo

個人公眾號:404Code,分享半個網際網路人的技術與思考,感興趣的可以關注.
404Code