spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

本文主要是介绍spring-gateway filters添加自定义过滤器实现流程分析(可插拔),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔)》:本文主要介绍spring-gatewayfilters添加自定义过滤器实现流程分析(可插拔),本文通过实例图...

需求背景

公司要求,通过公司网络代理访问的请求需要做请求隔离,即,通过特定代理IP访问的请求,需要除正常权限以外,还需要对请求路径,及特定路径下的请求参数做校验,通过校验正常访问,否则拒绝通过代理IP的请求。

需求拆解

1.针对特定的服务做限制;

2.特定的代理IP来源请求做校验;

3.特定的请求接口做限制;

4.特定的接口的特定的请求参数值做校验;

大致可以细分如上四点,在网关层统一实现需求,参考spring-gateway过滤器原理,个性化一个过滤器,按需配置即可;

设计流程及作用域

在配置网关路由策略时按需配置,灵活使用。

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

图2.1过滤器位置

自定义过滤器名称:InternalValidator

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

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判断

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

3.1逻辑处理

逻辑流程

spring-gateway filters添加自定义过滤器实现流程分析(可插拔)

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&China编程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.chinasem.cntry {
                                    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;
        pChina编程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自定义过滤器内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于spring-gateway filters添加自定义过滤器实现流程分析(可插拔)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154825

相关文章

springboot集成easypoi导出word换行处理过程

《springboot集成easypoi导出word换行处理过程》SpringBoot集成Easypoi导出Word时,换行符n失效显示为空格,解决方法包括生成段落或替换模板中n为回车,同时需确... 目录项目场景问题描述解决方案第一种:生成段落的方式第二种:替换模板的情况,换行符替换成回车总结项目场景s

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

SpringBoot中@Value注入静态变量方式

《SpringBoot中@Value注入静态变量方式》SpringBoot中静态变量无法直接用@Value注入,需通过setter方法,@Value(${})从属性文件获取值,@Value(#{})用... 目录项目场景解决方案注解说明1、@Value("${}")使用示例2、@Value("#{}"php

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践