Spring Cloud系列教程(十九):下一代網關服務Gateway-全局Filter(Finchley版本)

官網文檔: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

一、前言

Spring Cloud Gateway根據過濾器Filter的作用範圍劃分爲GatewayFilterGlobalFilter,二者區別如下:

GatewayFilter : GatewayFilter稱爲內置過濾器,需要通過 spring.cloud.routes.filters配置在具體路由下,只作用在當前路由上或者特定路由上,可以通過配置 spring.cloud.default-filters,表明作用在所有路由上,GatewayFilter允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器適用於特定路由。Spring Cloud Gateway提供了許多內置的GatewayFilter工廠。

GlobalFilter: GlobalFilter稱爲全局過濾器,不需要在配置文件中配置,作用在所有的路由上,全局有效。

二. GlobalFilter(全局過濾器)

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

Spring Cloud Gateway框架提供的內置的GlobalFilter如下:
在這裏插入圖片描述

三. GlobalFilter和GatewayFilter區別

GlobalFilter 接口和 GatewayFilter 有一樣的接口定義,只不過, GlobalFilter會作用於所有路由,我們可以用它來實現很多統一化處理的業務需求,比如權限認證,IP訪問限制等等;而GatewayFilter作用於某一個特定的路由, GlobalFilter全局過濾器是一系列特殊的過濾器,會根據條件應用到所有路由中。GatewayFilter網關過濾器是更細粒度的過濾器,作用於指定的路由中。

四. Gateway提供的GlobalFilter有哪些

gateway自帶的GlobalFilter實現類有很多,如下圖:
在這裏插入圖片描述
有轉發,路由,負載,WebSocket等相關的GlobalFilter,有興趣的小夥伴可以下載對應的源碼研究下。

五. 如何定義一個GlobalFilter

關於如何定義一個GlobalFilter實現自己的業務邏輯,Gateway官方給出了一個例子:

例子5.1. ExampleConfiguration.java

@Bean
public GlobalFilter customFilter() {
    return new CustomGlobalFilter();
}

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

    /**
     * 過濾器業務邏輯
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("custom global filter");
        return chain.filter(exchange);
    }
   
    /**
     * 過濾器執行順序,數值越小,優先級越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

六. GlobalFilter實現統一鑑權

定義一個AuthFilter過濾器實現 GlobalFilter, Ordered全局過濾器,實現統一鑑權,例如摘自ruoyi-cloud中的實現方案如下:

參考自: https://gitee.com/zhangmrit/ruoyi-cloud/

package com.ruoyi.gateway.fiflt;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
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.data.redis.core.ValueOperations;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 網關鑑權
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered
{
    // 排除過濾的 uri 地址
    // swagger排除自行添加
    private static final String[]           whiteList = {"/auth/login", "/user/register", "/system/v2/api-docs",
            "/auth/captcha/check", "/auth/captcha/get","/auth/login/slide"};

    @Resource(name = "stringRedisTemplate")
    private ValueOperations<String, String> ops;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        String url = exchange.getRequest().getURI().getPath();
        log.info("url:{}", url);
        // 跳過不需要驗證的路徑
        if (Arrays.asList(whiteList).contains(url))
        {
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst(Constants.TOKEN);
        // token爲空
        if (StringUtils.isBlank(token))
        {
            logger.info( "token is empty..." );
            return setUnauthorizedResponse(exchange, "token can't null or empty string");
        }
        String userStr = ops.get(Constants.ACCESS_TOKEN + token);
        if (StringUtils.isBlank(userStr))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        JSONObject jo = JSONObject.parseObject(userStr);
        String userId = jo.getString("userId");
        // 查詢token信息
        if (StringUtils.isBlank(userId))
        {
            return setUnauthorizedResponse(exchange, "token verify error");
        }
        // 設置userId到request裏,後續根據userId,獲取用戶信息
        ServerHttpRequest mutableReq = exchange.getRequest().mutate().header(Constants.CURRENT_ID, userId)
                .header(Constants.CURRENT_USERNAME, jo.getString("loginName")).build();
        ServerWebExchange mutableExchange = exchange.mutate().request(mutableReq).build();
        return chain.filter(mutableExchange);
    }

    private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange, String msg)
    {
        ServerHttpResponse originalResponse = exchange.getResponse();
        originalResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
        originalResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        byte[] response = null;
        try
        {
            response = JSON.toJSONString(R.error(401, msg)).getBytes(Constants.UTF8);
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        DataBuffer buffer = originalResponse.bufferFactory().wrap(response);
        return originalResponse.writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder()
    {
        return -200;
    }
}

在上面的AuthFilter需要實現GlobalFilter和Ordered接口,這和實現GatewayFilter很類似。然後根據ServerWebExchange獲取ServerHttpRequest,再根據ServerHttpRequest中是否含有參數token,如果沒有則完成請求,終止轉發,否則執行正常的邏輯。

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