SpringCloud Gateway实现请求解密和响应加密
文章目录
- 前言
- 正文
- 一、项目简介
- 二、核心代码
- 2.1 自定义过滤器
- 2.2 网关配置
- 2.3 自定义配置类
- 2.4 加密组件接口
- 2.5 加密组件实现,AES算法
- 2.6 启动类,校验支持的算法配置
- 三、请求报文示例
- 四、测试结果
- 4.1 网关项目启动时
- 4.2 发生请求时
前言
本文环境使用比较新的 Java 17 和 SpringBoot 3.1.5,对应到Spring的版本是 6.0.13
使用到的三方插件有:
- lombok
- gson
- hutool
本文注重实现请求的解密和响应的加密,加解密使用的是 Hutool 中的工具类,加解密算法目前提供了AES的方式,其余方式也可兼容扩展。
完整代码仓库:https://gitee.com/fengsoshuai/springcloud-gateway-feng-demo
借用网关中的过滤器GlobalFilter来实现这一功能。
本文只粘贴一些重点文件内容。
正文
一、项目简介

在聚合项目中,有两个核心模块,feng-server提供了 rest 接口,供网关使用。
feng-gateway 是核心实现的网关项目,实现了自定义过滤器,以及增加了一些基本配置功能。本文重心是网关项目。
二、核心代码
2.1 自定义过滤器
package org.feng.fenggateway.filters;import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.feng.fenggateway.config.SecureProperties;
import org.feng.fenggateway.dto.ResponseDto;
import org.feng.fenggateway.secure.SecureComponent;
import org.feng.fenggateway.secure.SecureComponentFactory;
import org.feng.fenggateway.util.GsonUtil;
import org.reactivestreams.Publisher;
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.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.Set;/*** 自定义密文过滤器** @author feng*/
@Slf4j
@Component
public class CustomCipherTextFilter implements GlobalFilter, Ordered {@Resourceprivate SecureProperties secureProperties;private SecureComponent secureComponent;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取请求体ServerHttpRequest request = exchange.getRequest();// 获取响应体ServerHttpResponse response = exchange.getResponse();// 请求头HttpHeaders headers = request.getHeaders();// 请求方法HttpMethod method = request.getMethod();// 满足条件,进行过滤if (isNeedFilterMethod(method) && isNeedFilterContentType(headers.getContentType())) {return DataBufferUtils.join(request.getBody()).flatMap(dataBuffer -> {try {// 获取请求参数String originalRequestBody = getOriginalRequestBody(dataBuffer);// 解密请求参数String decryptRequestBody = decryptRequest(originalRequestBody);// 装饰新的请求体ServerHttpRequestDecorator requestDecorator = serverHttpRequestDecorator(request, decryptRequestBody);// 装饰新的响应体ServerHttpResponseDecorator responseDecorator = serverHttpResponseDecorator(response);// 使用新的请求和响应转发ServerWebExchange serverWebExchange = exchange.mutate().request(requestDecorator).response(responseDecorator).build();// 放行拦截return chain.filter(serverWebExchange);} catch (Exception e) {log.error("密文过滤器加解密错误", e);return Mono.empty();} finally {DataBufferUtils.release(dataBuffer);}});}return chain.filter(exchange);}private String decryptRequest(String originalRequestBody) {if (!secureProperties.enableDecryptRequestParam()) {log.info("请求参数解密,跳过");return originalRequestBody;}log.info("请求参数解密,原文:{}", originalRequestBody);String decrypted = getSecureComponent().decrypt(originalRequestBody);log.info("请求参数解密,明文:{}", decrypted);return decrypted;}private String encryptResponse(String originalResponseBody) {if (!secureProperties.enableEncryptResponseParam()) {log.info("响应结果加密,跳过");return originalResponseBody;}ResponseDto responseDto = GsonUtil.fromJson(originalResponseBody, ResponseDto.class);// 只对data字段进行加密处理Object data = responseDto.getData();if (Objects.nonNull(data)) {responseDto.setData(getSecureComponent().encrypt(data.toString()));}log.info("响应结果加密,原文:{}", originalResponseBody);String result = GsonUtil.toJson(responseDto);log.info("响应结果加密,密文:{}", result);return result;}/*** 获取原始的请求参数** @param dataBuffer 数据缓冲* @return 原始的请求参数*/private String getOriginalRequestBody(DataBuffer dataBuffer) {byte[] bytes = new byte[dataBuffer.readableByteCount()];dataBuffer.read(bytes);return new String(bytes, StandardCharsets.UTF_8);}private boolean isNeedFilterMethod(HttpMethod method) {return NEED_FILTER_METHOD_SET.contains(method);}private boolean isNeedFilterContentType(MediaType mediaType) {return NEED_FILTER_MEDIA_TYPE_SET.contains(mediaType) || "json".equals(mediaType.getSubtype());}private ServerHttpRequestDecorator serverHttpRequestDecorator(ServerHttpRequest originalRequest, String decryptRequestBody) {return new ServerHttpRequestDecorator(originalRequest) {@Overridepublic HttpHeaders getHeaders() {HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.putAll(super.getHeaders());httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");return httpHeaders;}@Overridepublic Flux<DataBuffer> getBody() {byte[] bytes = decryptRequestBody.getBytes(StandardCharsets.UTF_8);return Flux.just(new DefaultDataBufferFactory().wrap(bytes));}};}private ServerHttpResponseDecorator serverHttpResponseDecorator(ServerHttpResponse originalResponse) {DataBufferFactory dataBufferFactory = originalResponse.bufferFactory();return new ServerHttpResponseDecorator(originalResponse) {@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {if (body instanceof Flux<? extends DataBuffer> fluxBody) {return super.writeWith(fluxBody.buffer().map(dataBuffers -> {DataBuffer join = dataBufferFactory.join(dataBuffers);byte[] byteArray = new byte[join.readableByteCount()];join.read(byteArray);DataBufferUtils.release(join);String originalResponseBody = new String(byteArray, StandardCharsets.UTF_8);//加密byte[] encryptedByteArray = encryptResponse(originalResponseBody).getBytes(StandardCharsets.UTF_8);originalResponse.getHeaders().setContentLength(encryptedByteArray.length);originalResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);return dataBufferFactory.wrap(encryptedByteArray);}));}return super.writeWith(body);}@Overridepublic Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {return writeWith(Flux.from(body).flatMapSequential(p -> p));}@Overridepublic HttpHeaders getHeaders() {HttpHeaders headers = new HttpHeaders();headers.putAll(originalResponse.getHeaders());return headers;}};}private static final Set<HttpMethod> NEED_FILTER_METHOD_SET = Set.of(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);private static final Set<MediaType> NEED_FILTER_MEDIA_TYPE_SET = Set.of(MediaType.APPLICATION_JSON);@Overridepublic int getOrder() {return -1;}public SecureComponent getSecureComponent() {if (Objects.isNull(secureComponent)) {secureComponent = SecureComponentFactory.get(secureProperties.getAlgorithm());}return secureComponent;}
}
2.2 网关配置
server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称cloud:gateway:routes: # 网关路由配置- id: feng-server1 # 路由id,自定义,只要唯一即可uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/server/list/server1/** # 这个是按照路径匹配,只要以/user/开头就符合要求- id: feng-server2uri: http://127.0.0.1:8082predicates:- Path=/server/list/server2/**# 自定义配置
feng:gateway:secure:request-switch:enable: falseresponse-switch:enable: truealgorithm: aes
2.3 自定义配置类
package org.feng.fenggateway.config;import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.feng.fenggateway.secure.SecureComponentFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.Objects;/*** 加解密属性配置** @author feng*/
@Slf4j
@Data
@ConfigurationProperties(prefix = SecureProperties.SECURE_PROPERTIES_PREFIX)
public class SecureProperties {public static final String SECURE_PROPERTIES_PREFIX = "feng.gateway.secure";/*** 算法*/private SymmetricAlgorithm algorithm;/*** 请求开关*/private SecureSwitch requestSwitch;/*** 响应开关*/private SecureSwitch responseSwitch;public void checkSupportedAlgorithm() {log.info("校验是否支持算法:{}", algorithm);if (Objects.isNull(algorithm)) {return;}boolean supportedAlgorithm = SecureComponentFactory.isSupportedAlgorithm(algorithm);if (!supportedAlgorithm) {throw new UnsupportedOperationException("不支持的算法");}log.info("校验是否支持算法:校验通过");}/*** 是否启用解密请求参数** @return 默认为否,其他情况看配置*/public boolean enableDecryptRequestParam() {if (Objects.isNull(requestSwitch)) {return false;}return requestSwitch.getEnable();}/*** 是否启用加密响应参数** @return 默认为否,其他情况看配置*/public boolean enableEncryptResponseParam() {if (Objects.isNull(responseSwitch)) {return false;}return responseSwitch.getEnable();}
}
2.4 加密组件接口
这个可以用来扩展支持其他加密算法,目前实现类只有AES。
package org.feng.fenggateway.secure;import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import jakarta.annotation.PostConstruct;/*** 加解密组件** @author feng*/
public interface SecureComponent {/*** 加密** @param originalText 原文* @return 密文*/String encrypt(String originalText);/*** 解密** @param encryptedText 密文* @return 解密后的明文*/String decrypt(String encryptedText);/*** 获取加解密算法类型** @return 加解密算法类型*/SymmetricAlgorithm getAlgorithmType();@PostConstructdefault void registerToFactory() {SecureComponentFactory.registerBean(this);}
}
2.5 加密组件实现,AES算法
package org.feng.fenggateway.secure;import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import org.springframework.stereotype.Component;import java.nio.charset.StandardCharsets;/*** AES加解密组件** @author feng*/
@Component
public class SecureAESComponent implements SecureComponent {/*** 生成密钥,16、24、32位都行*/private final static byte[] SECURE_KEY = "r4oz0f3kfk5tgyui".getBytes(StandardCharsets.UTF_8);/*** 偏移量,必须16位*/private final static String IV = "r21g95kdsd423gy6";private final static AES AES_INSTANCE = new AES(Mode.CTS, Padding.PKCS5Padding, SECURE_KEY, IV.getBytes(StandardCharsets.UTF_8));@Overridepublic String encrypt(String originalText) {return AES_INSTANCE.encryptHex(originalText);}@Overridepublic String decrypt(String encryptedText) {return AES_INSTANCE.decryptStr(encryptedText);}@Overridepublic SymmetricAlgorithm getAlgorithmType() {return SymmetricAlgorithm.AES;}
}
2.6 启动类,校验支持的算法配置
package org.feng.fenggateway;import jakarta.annotation.Resource;
import org.feng.fenggateway.config.SecureProperties;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;@ConfigurationPropertiesScan
@SpringBootApplication
public class FengGatewayApplication implements CommandLineRunner {@Resourceprivate SecureProperties secureProperties;public static void main(String[] args) {SpringApplication.run(FengGatewayApplication.class, args);}@Overridepublic void run(String... args) {secureProperties.checkSupportedAlgorithm();}
}
三、请求报文示例
POST http://localhost:10010/server/list/server2/user?authorization=feng
Content-Type: application/json;charset=UTF-8{"username": "fbb"
}
四、测试结果
4.1 网关项目启动时
校验结果正常:

4.2 发生请求时
可以看到data字段已经加密响应了。

请求和响应结果:

相关文章:
SpringCloud Gateway实现请求解密和响应加密
文章目录 前言正文一、项目简介二、核心代码2.1 自定义过滤器2.2 网关配置2.3 自定义配置类2.4 加密组件接口2.5 加密组件实现,AES算法2.6 启动类,校验支持的算法配置 三、请求报文示例四、测试结果4.1 网关项目启动时4.2 发生请求时 前言 本文环境使用比…...
IDEA创建Springboot多模块项目
一、创建父模块 File --> New --> Project ,选择 “ Spring Initalizr ” ,点击 Next Next Next --> Finish 二、创建子模块 右键根目录,New --> Module 选择 “ Spring Initializr ”,点击Next 此处注意T…...
React:JSX语法入门
JSX语法入门及代码 JSX是一种JavaScript的语法扩展,用于在React中描述用户界面的结构。它允许开发者使用类似HTML的语法来创建React元素,使得代码更具可读性和可维护性。JSX将HTML标签和JavaScript代码结合在一起,可以在其中使用JavaScript表…...
AI大模型架构师专家,你会问什么来测试我的水平,如何解答上述问题,学习路径是什么
0. 沈剑老师的大模型产品应用经验: 提示词三步骤: 假如我是xxx专家,你会问什么来测试我的水平;假如你是xxx专家,你会如何解答上述问题;假如你是xxx专家,上述问题的学习路径是什么;…...
Dev-C调试的基本方法2-1
在Dev-C中调试程序,首先需要在程序中设置断点,之后以调试的方式运行程序。 1 设置断点 当以调试的方式运行程序时,程序会在断点处停下来。点击要设置断点代码行号左侧部分,此时会有如图1所示的红点和绿色对勾,表示断…...
Linux 调试 (objdump/strace/strings)
目录 1. Linux 调试 (objdump/strace/strings)1.1. 查看系统 glibc 版本号1.2. 查看 so/bin 中的依赖1.3. 调试 bin 报错原因1.4. 查看 so/bin 中字符串 1. Linux 调试 (objdump/strace/strings) 1.1. 查看系统 glibc 版本号 1. 第一种 # ldd --version ldd (Ubuntu GLIBC 2.…...
CAS 单点登录详解
什么是CAS? - 简要介绍CAS(Central Authentication Service)的概念和作用。 - 强调CAS的主要目标是提供单点登录和单点登出功能。 CAS的工作原理 - 深入探讨CAS的工作原理,包括认证、票据、验证等核心概念。 - 详细解释CAS服务器和…...
tbh常用的绘图快捷键
1、Altb -> 笔刷 2、Alt/ -> 画笔 3、按住Shift 绘出的线条是直线 4、按住shiftalt 绘出来的线条是水平线或垂直线 5、alte ->橡皮擦 6、alts ->选择工具 7、altq -> 轮廓编辑器 以下操作都是在选中轮廓编辑器下操作的: 按住alt…...
Android-Framework 清除应用用户数据,不清除权限
一、环境 高通865 Android 10 二、具体详情 现象:默认赋予第三方应用的权限,在应用信息中清理用户数据的时候,权限也会被清理掉; 希望:只清理数据,保留权限 相关源码修改如下: frameworks/base/servi…...
CS认证办理流程,CS认证好处
CS资质全名叫“信息系统建设和服务能力评估体系” 所谓的CS认证,是指信息系统建设和服务能力评级。认证一共划分为五个等级,等级从低到高分别用CS1级(初始级)、CS2级(基本级)、CS3级(良好级&…...
macOS 安装brew
参考链接: https://mirrors4.tuna.tsinghua.edu.cn/help/homebrew/ https://www.yii666.com/blog/429332.html 安装中科大源的: https://zhuanlan.zhihu.com/p/470873649...
H5: 使用Web Audio API播放音乐
简介 记录关于自己使用 Web Audio API 的 AudioContext 播放音乐的知识点。 需求分析 1.列表展示音乐; 2.上/下一首、播放/暂停/续播; 3.播放模式切换:循环播放、单曲循环、随机播放; 4.播放状态显示:当前播放的音乐…...
Parasoft C/C++test:汽车网络安全ISO 21434最佳实践
为什么汽车网络安全很重要Why Automotive Cybersecurity Is Important 许多汽车公司向电子道路车辆的转变从根本上改变了整个行业,提高了汽车的互联性和智能性。随着电子汽车变得更加互联和智能,它们也越来越依赖软件来实现车辆操作,驱动更多…...
如何卸载干净 IDEA(图文讲解)windows和Mac教程
大家好,我是sun~ 很多小伙伴会问 Windows / Mac 系统上要怎么彻底卸载 IDEA 呢? 本文通过图片文字,详细讲解具体步骤: 如何卸载干净 IDEA(图文讲解) Windows1、卸载 IDEA 程序2、注册表清理3、残留清理 M…...
Docker搭建Gitlab
拉取镜像:docker pull gitlab/gitlab-ce创建映射目录: mkdir -p /usr/local/gitlab/config mkdir -p /usr/local/gitlab/data mkdir -p /usr/local/gitlab/logs运行容器: docker run -d -p 443:443 -p 8000:8000 -p 222:22 --name gitlab …...
STM32F4X SDIO(四) SDIO控制器
STM32F4X SDIO(四) SDIO控制器 STM32F4X SDIO控制器SDIO控制器框图SDIO控制器时钟适配器寄存器FIFO控制单元命令路径数据路径 SDIO寄存器SDIO控制相关寄存器SDIO电源控制寄存器 (SDIO_POWER)SDIO时钟控制寄存器 (SDIO_CLKCR)SDIO_CK相位 SDIO命令响应相关…...
【flink】Task 故障恢复详解以及各重启策略适用场景说明
文章目录 一. 重启策略种类(Restart Strategies)1. Fixed Delay Restart Strategy2. Failure Rate Restart Strategy3. Fallback Restart Strategy4. No Restart Strategy 二. 故障恢复策略(Failover Strategies)1. (全…...
一个计算机高手的成长3
这是转在茶余的帖子。文中绝大部分技术术语我不懂,所以无资格评论他的技术价值。但文章强烈的逻辑说服力,和通篇流露的进取精神,使我觉得这是篇有价值的帖子,至少值得一读。 就像我开始从MIS转到通信一样,我看过大量通…...
2023应届生能力考试含解析(Java后端开发)——(1)
1.以下代码的循环次数是 ( ) public class Test {public static void main(String[] args) {int i 7;do {System.out.println(--i);--i;} while (i ! 0);System.out.println(i);} } A 0 B 1 C 7 D 无限次 这段代码会导致无限循环的原因是在 do-while 循环中&#…...
Ansible中的任务执行控制
循环 简单循环 {{item}} 迭代变量名称 loop: - value1 - value2 - ... //赋值列表{{item}} //迭代变量名称循环散列或字典列表 - name: create filehosts: host1tasks:- name: file moudleservice:name: "{{ item.name }}"state: "{{…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南
在RK3588上搭建ROS1环境:创建节点与数据可视化实战指南 背景介绍完整操作步骤1. 创建Docker容器环境2. 验证GUI显示功能3. 安装ROS Noetic4. 配置环境变量5. 创建ROS节点(小球运动模拟)6. 配置RVIZ默认视图7. 创建启动脚本8. 运行可视化系统效果展示与交互技术解析ROS节点通…...
LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考
目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...
