spring-gateway filters添加自定義過濾器實(shí)現(xiàn)流程分析(可插拔)
需求背景
公司要求,通過公司網(wǎng)絡(luò)代理訪問的請求需要做請求隔離,即,通過特定代理IP訪問的請求,需要除正常權(quán)限以外,還需要對請求路徑,及特定路徑下的請求參數(shù)做校驗(yàn),通過校驗(yàn)正常訪問,否則拒絕通過代理IP的請求。
需求拆解
1.針對特定的服務(wù)做限制;
2.特定的代理IP來源請求做校驗(yàn);
3.特定的請求接口做限制;
4.特定的接口的特定的請求參數(shù)值做校驗(yàn);
大致可以細(xì)分如上四點(diǎn),在網(wǎng)關(guān)層統(tǒng)一實(shí)現(xiàn)需求,參考spring-gateway過濾器原理,個(gè)性化一個(gè)過濾器,按需配置即可;
設(shè)計(jì)流程及作用域
在配置網(wǎng)關(guān)路由策略時(shí)按需配置,靈活使用。

圖2.1過濾器位置
自定義過濾器名稱:InternalValidator

2.2spring-gateway使用配置示例
過濾器設(shè)計(jì)
1.name : InternalValidator 過濾器id (必須)
2.args : 過濾器參數(shù) (個(gè)性化實(shí)現(xiàn)時(shí)必須)
2.1 paths: /api/**,/** 請求路徑校驗(yàn)(通配符),前提條件1
2.2 limited-from: '172.24.173.62,172.24.172.252' 模擬代理IP地址,前提條件2
2.3 conditions: 條件過濾,這是個(gè)個(gè)性化配置集合
2.3.1 paths: /api/** 條件集合判斷路徑校驗(yàn)(通配符)
2.3.2 body-param: 'code' 請求體參數(shù)名{'code':'159300'}
2.3.3 body-value:'159300' 參數(shù)名對應(yīng)的參數(shù)值
2.3.4 path-param: 'name' 路徑參數(shù)名 ?name=zhangsan
2.3.5 path-value: 'zhangsan' 路徑參數(shù)值
3.excludes:'/api/login/*' 排除路徑(通配符)
邏輯處理
1.來自代理IP判斷

3.1邏輯處理
邏輯流程

3.2流程處理
代碼邏輯
1.名稱必須是InternalValidator + GatewayFilterFactory
2.config服從駝峰命名轉(zhuǎn)換 即limited-from : limitedFrom
3.對config映射屬性必須要有set方法
@Component
@Slf4j
public class InternalValidatorGatewayFilterFactory extends AbstractGatewayFilterFactory<InternalValidatorGatewayFilterFactory.Config> implements Ordered {
private final AntPathMatcher antPathMatcher = new AntPathMatcher();
public InternalValidatorGatewayFilterFactory() {
super(Config.class); // 指定配置類
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
Set<String> limitedIps = config.getLimitedIps();
ServerHttpRequest request = exchange.getRequest();
//接口路徑
String clientIp = IPHelp.getClientIp(request);
if (!isLimitedIp(clientIp, limitedIps)) {
return chain.filter(exchange);
}
String path = request.getPath().value();
Set<String> includesPaths = config.getIncludesPaths();
if (!pathMatch(path, includesPaths)) {
log.info(" --> includesPaths: {}, 不包含: 請求路徑:{},", includesPaths, path);
return chain.filter(exchange);
}
Set<String> excludesPaths = config.getExcludesPaths();
if (pathMatch(path, excludesPaths)) {
log.info(" --> excludesPaths: {}, contains: 請求路徑:{},", excludesPaths, path);
return chain.filter(exchange);
}
Map<String, Map<String, String>> pathParamValueMap = config.getPathParamValueMap();
Map<String, Map<String, String>> bodyParamValueMap = config.getBodyParamValueMap();
Map<String, String> bodyParamMap = getPathParamValueMap(path, bodyParamValueMap);
Map<String, String> queryParamMap = getPathParamValueMap(path, pathParamValueMap);
if ((bodyParamMap == null || bodyParamMap.isEmpty()) && (
queryParamMap == null || queryParamMap.isEmpty()
)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
if (queryParamMap != null && !queryParamMap.isEmpty()) {
MultiValueMap<String, String> queryParams = request.getQueryParams();
if (!queryParamMatch(queryParamMap, queryParams)) {
log.info(" --> path: {}, bodyParamConditions: {}, queryParamMap: {},校驗(yàn)失敗!", path, bodyParamMap, queryParamMap);
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
if (bodyParamMap == null || bodyParamMap.isEmpty()) {
return chain.filter(exchange);
}
}
if (bodyParamMap != null && !bodyParamMap.isEmpty()) {
String method = request.getMethodValue();
if ("POST".equals(method)) {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
try {
String bodyString = new String(bytes, "utf-8");
if (!bodyParamMatch(bodyParamMap, bodyString)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
} catch (UnsupportedEncodingException e) {
}
DataBufferUtils.release(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(bytes);
return Mono.just(buffer);
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
}
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
};
}
@Override
public int getOrder() {
return 2;
}
private Map<String, String> getPathParamValueMap(String path, Map<String, Map<String, String>> paramValueMap) {
if (paramValueMap == null || paramValueMap.isEmpty()) {
return null;
}
Map<String, String> res = new HashMap<>();
for (Map.Entry<String, Map<String, String>> paramValueEntry : paramValueMap.entrySet()) {
String pathPredicate = paramValueEntry.getKey();
if (antPathMatcher.match(pathPredicate, path)) {
Map<String, String> paramValue = paramValueEntry.getValue();
if (paramValue == null) {
continue;
}
res.putAll(paramValue);
}
}
return res;
}
private String extractParam(String jsonStr, String key) {
try {
int startIndex = jsonStr.indexOf("\"" + key + "\"");
if (startIndex != -1) {
int valueStart = jsonStr.indexOf(':', startIndex);
int valueEnd = jsonStr.indexOf(',', valueStart);
if (valueEnd == -1) {
valueEnd = jsonStr.indexOf('}', valueStart);
}
if (valueStart != -1 && valueEnd != -1) {
String valuePart = jsonStr.substring(valueStart + 1, valueEnd).trim();
return valuePart.replaceAll("\"", "");
}
}
} catch (Exception e) {
// 可以記錄日志
return null;
}
return null;
}
private boolean pathMatch(String path, Set<String> predicatePaths) {
if (predicatePaths == null || predicatePaths.isEmpty()) {
return false;
}
return predicatePaths.stream()
.anyMatch(predicatePath -> antPathMatcher.match(predicatePath, path));
}
private boolean paramMatch(Map<String, String> bodyParamMap,
Map<String, String> queryParamMap,
ServerHttpRequest request,
ServerWebExchange exchange
) {
return true;
}
private boolean bodyParamMatch(Map<String, String> bodyParamConditions, String bodyStr) {
if (bodyStr == null) {
return false;
}
for (Map.Entry<String, String> conditionMapEntry : bodyParamConditions.entrySet()) {
String predicateParam = conditionMapEntry.getKey();
String predicateValue = conditionMapEntry.getValue();
String acValue = extractParam(bodyStr, predicateParam);
if (!predicateValue.equals(acValue)) {
log.info(" --> bodyParamMatch:::predicateParam:{}, predicateValue:{}, acValue:{},參數(shù)校驗(yàn)不通過!",
predicateParam, predicateValue, acValue);
return false;
}
}
return true;
}
private boolean queryParamMatch(Map<String, String> conditionMap, MultiValueMap<String, String> queryParams) {
for (Map.Entry<String, String> conditionMapEntry : conditionMap.entrySet()) {
String predicateParam = conditionMapEntry.getKey();
String predicateValue = conditionMapEntry.getValue();
String acValue = queryParams.getFirst(predicateParam);
if (!predicateValue.equals(acValue)) {
log.info(" --> queryParamMatch:::predicateParam:{}, predicateValue:{}, acValue:{},參數(shù)校驗(yàn)不通過!",
predicateParam, predicateValue, acValue);
return false;
}
}
return true;
}
private boolean isLimitedIp(String clientIp, Set<String> limitedIps) {
if (clientIp == null || clientIp.isEmpty()) {
log.warn(" --> clientIp:{} 為空!", clientIp);
return true;
}
if (limitedIps == null || limitedIps.isEmpty()) {
log.info(" --> limitedIps:{} 為空!", limitedIps);
return false;
}
for (String limitedIp : limitedIps) {
if (clientIp.contains(limitedIp)) {
return true;
}
}
return false;
}
// 配置類
public static class Config {
private String paths = "/**";
private String limitedFrom;
private List<ConditionConfig> conditions = new ArrayList<>();
private String excludes;
private Set<String> includesPaths;
private Set<String> limitedIps = new HashSet<>();
private Set<String> excludesPaths = new HashSet<>();
private Map<String, Map<String, String>> bodyParamValueMap = new HashMap<>();
private Map<String, Map<String, String>> pathParamValueMap = new HashMap<>();
public void setPaths(String paths) {
this.paths = paths;
if (paths != null) {
this.includesPaths = new HashSet<>(Arrays.asList(paths.split(",")));
}
}
public void setLimitedFrom(String limitedFrom) {
this.limitedFrom = limitedFrom;
if (limitedFrom != null) {
this.limitedIps = new HashSet<>(Arrays.asList(limitedFrom.split(",")));
}
}
public void setConditions(List<ConditionConfig> conditions) {
this.conditions = conditions;
if (conditions != null && !conditions.isEmpty()) {
for (ConditionConfig condition : conditions) {
String conditionPaths = condition.getPaths();
String bodyParam = condition.getBodyParam();
String bodyValue = condition.getBodyValue();
String pathParam = condition.getPathParam();
String pathValue = condition.getPathValue();
if (conditionPaths != null) {
if (bodyParam != null && bodyValue != null) {
for (String path : conditionPaths.split(",")) {
Map<String, String> bodyParamCondition = bodyParamValueMap.get(path);
if (bodyParamCondition == null) {
bodyParamCondition = new HashMap<>();
}
bodyParamCondition.put(bodyParam, bodyValue);
bodyParamValueMap.put(path, bodyParamCondition);
}
}
if (pathParam != null && pathValue != null) {
for (String path : conditionPaths.split(",")) {
Map<String, String> pathParamCondition = pathParamValueMap.get(path);
if (pathParamCondition == null) {
pathParamCondition = new HashMap<>();
}
pathParamCondition.put(pathParam, pathValue);
pathParamValueMap.put(path, pathParamCondition);
}
}
}
}
}
}
public void setExcludes(String excludes) {
this.excludes = excludes;
if (excludes != null) {
this.excludesPaths = new HashSet<>(Arrays.asList(excludes.split(",")));
}
}
public Set<String> getIncludesPaths() {
return includesPaths;
}
public Set<String> getLimitedIps() {
return limitedIps;
}
public Set<String> getExcludesPaths() {
return excludesPaths;
}
public Map<String, Map<String, String>> getBodyParamValueMap() {
return bodyParamValueMap;
}
public Map<String, Map<String, String>> getPathParamValueMap() {
return pathParamValueMap;
}
}
@Data
public static class ConditionConfig {
private String paths;
private String bodyParam;
private String bodyValue;
private String pathParam;
private String pathValue;
}
}借助了工具類IPHelp.java
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Slf4j
public class IPHelp {
private static final List<String> IP_HEADERS = Collections.unmodifiableList(Arrays.asList(
"X-Forwarded-For",
"x-forwarded-for",
"xff",
"x-real-ip",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR"
));
public static String getClientIp(ServerHttpRequest request) {
String resultIp = null;
HttpHeaders headers = request.getHeaders();
for (String head : IP_HEADERS) {
String ip = headers.getFirst(head);
log.info(" --> IP_HEADER:{}, IP_VALUE:{}", head, ip);
if (isValidIp(ip)) {
resultIp = ip;
break;
}
}
if (resultIp == null && request.getRemoteAddress() != null) {
InetSocketAddress remoteAddress = request.getRemoteAddress();
resultIp = remoteAddress.getAddress().getHostAddress();
log.info(" --> IP_HEADER: remoteAddress, IP_VALUE:{}", resultIp);
}
log.info(" --> getClientIp, IP_VALUE:{}", resultIp);
return resultIp;
}
private static boolean isValidIp(String ip) {
return ip != null && !ip.trim().isEmpty() && !"unknown".equalsIgnoreCase(ip.trim());
}
}RESPECT!
到此這篇關(guān)于spring-gateway filters添加自定義過濾器實(shí)現(xiàn)(可插拔)的文章就介紹到這了,更多相關(guān)spring-gateway filters自定義過濾器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring+Vue整合UEditor富文本實(shí)現(xiàn)圖片附件上傳的方法
這篇文章主要介紹了Spring+Vue整合UEditor富文本實(shí)現(xiàn)圖片附件上傳的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Java使用Lettuce客戶端在Redis在主從復(fù)制模式下命令執(zhí)行的操作
這篇文章主要介紹了Java使用Lettuce客戶端在Redis在主從復(fù)制模式下命令執(zhí)行的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
IntelliJ IDEA中Project與Module的概念以及區(qū)別
這篇文章主要給大家介紹了關(guān)于IntelliJ IDEA中Project與Module的概念以及區(qū)別的相關(guān)資料,文中通過實(shí)例介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
java實(shí)現(xiàn)interceptor攔截登錄權(quán)限
Java里的攔截器是動(dòng)態(tài)攔截action調(diào)用的對象,本文主要介紹了java實(shí)現(xiàn)interceptor攔截登錄權(quán)限,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09
spring啟動(dòng)后保證創(chuàng)建的對象不被垃圾回收器回收
最近看到一個(gè)問題是,spring在啟動(dòng)后如何保證創(chuàng)建的對象不被垃圾回收器回收?。所以本文結(jié)合jvm的垃圾回收機(jī)制和spring中的源代碼做出自己的一點(diǎn)猜測。有需要的朋友們可以參考借鑒。2016-09-09
SpringBoot參數(shù)驗(yàn)證的幾種方式小結(jié)
在日常的接口開發(fā)中,為了防止非法參數(shù)對業(yè)務(wù)造成影響,經(jīng)常需要對接口的參數(shù)進(jìn)行校驗(yàn),例如登錄的時(shí)候需要校驗(yàn)用戶名和密碼是否為空,所以本文介紹了SpringBoot參數(shù)驗(yàn)證的幾種方式,需要的朋友可以參考下2024-07-07
本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題最新解決方案
這篇文章主要介紹了本地啟動(dòng)RocketMQ未映射主機(jī)名產(chǎn)生的超時(shí)問題,本文給大家分享最新解決方案,感興趣的朋友跟隨小編一起看看吧2024-02-02

