官網文檔: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
一、前言
Spring Cloud Gateway
根據過濾器Filter的作用範圍劃分爲GatewayFilter
和 GlobalFilter
,二者區別如下:
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,如果沒有則完成請求,終止轉發,否則執行正常的邏輯。