目录
- 需求背景
- 需求拆解
- 设计流程及作用域
- 逻辑处理
- 代码逻辑
需求背景
公司要求,通过公司网络代理访问的请求需要做请求隔离,即,通过特定代理IP访问的请求,需要除正常权限以外,还需要对请求路径,及特定路径下的请求参数做校验,通过校验正常访问,否则拒绝通过代理IP的请求。
需求拆解
1.针对特定的服务做限制;
2.特定的代理IP来源请求做校验;
3.特定的请求接口做限制;
4.特定的接口的特定的请求参数值做校验;
大致可以细分如上四点,在网关层统一实现需求,参考spring-gateway过滤器原理,个性化一个过滤器,按需配置即可;
设计流程及作用域
在配置网关路由策略时按需配置,灵活使用。

图2.1过滤器位置
自定义过滤器名称:InternalValidator

2.2spring-gateway使用配置示例
过滤器设计
1.name : InternalValidator 过滤器id (必须)
2.args : 过滤器参数 (个性化实现时必须)
2.1 paths: /api/**,/** 请求路径校验(通配符),前提条件1
2.2 limited-from: '172.24.173.62,172.24.172.252' 模拟代理IP地址,前提条件2
2.3 conditions: 条件过滤,这是个个性化配置集合
2.3.1 paths: /api/** 条件集合判断路径校验(通配符)
2.3.2 body-param: 'code' 请求体参数名{'code':'159300'}
2.3.3 body-value:'159300' 参数名对应的参数值
2.3.4 path-param: 'name' 路径参数名 ?name=zhangsan
2.3.5 path-value: 'zhangsan' 路径参数值
3.excludes:'/api/login/*' 排除路径(通配符)
逻辑处理
1.来自代理IP判断

3.1逻辑处理
逻辑流程

3.2流程处理
代码逻辑
1.名称必须是InternalValidator + GatewayFilterFactory
2.config服从驼峰命名转换 即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&编程客栈lt;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: {},校验失败!", 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);
www.devze.comtry {
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;
}
};
js 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:{},参数校验不通过!",
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:{},参数校验不通过!",
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;
p编程客栈rivate 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 = bodyjavascriptParamValueMap.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!
到此这篇关于spring-gateway filters添加自定义过滤器实现(可插拔)的文章就介绍到这了,更多相关spring-gateway filters自定义过滤器内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论