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字段已经加密响应了。
在这里插入图片描述

请求和响应结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/127575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UI动效的都可以用哪些工作来制作

随着UI设计的不断发展&#xff0c;UI动效越来越多地应用于现实生活中。手机&#xff0c;iPad、计算机、网页和其他设备被广泛使用&#xff0c;所以问题来了&#xff0c;为什么UI动态效果越来越被广泛使用&#xff1f;它的优点是什么&#xff1f;哪些软件可以设计UI动态效果&…

车载测试相比软件测试,前景会稍好一点吗?

> 如果个人是汽车、电气、工业工程相关专业的学历背景&#xff0c;那可以考虑从事车载测试&#xff08;看上图&#xff09;。> 如果不是以上专业&#xff0c;那就要慎重啦。 车载测试是测试行业的一个分支&#xff0c;最近十年一直存在&#xff0c;并不是这一两年才有的…

python 数据挖掘库orange3 介绍

orange3 是一个非常适合初学者的data mining library. 它让使用者通过拖拽内置的组件来形成工作流。让你不需要写任何代码就可以体验到数据挖掘和可视化的魅力。 它的桌面如下&#xff0c;这里我创建了 3 个节点&#xff0c;分别是数据集、小提琴图&#xff0c;散点图 其中 …

UE5——网络——RPC

RPC&#xff08;这个是官方文档的资料&#xff09; 要将一个函数声明为 RPC&#xff0c;您只需将 Server、Client 或 NetMulticast 关键字添加到 UFUNCTION 声明。 例如&#xff0c;若要将某个函数声明为一个要在服务器上调用、但需要在客户端上执行的 RPC&#xff0c;您可以…

【Linux】配置JDKTomcat开发环境及MySQL安装和后端项目部署

目录 一. JDK及tomcat安装 二&#xff0c;安装Tomcat 三&#xff0c;MySQL安装 四、后端部署 前言&#xff1a; 今天我们就来在Linux上安装JDK及tomcat&#xff0c;MySQL&#xff0c;希望你可以通过这一博客&#xff0c;找到你的答案&#xff01;&#xff01;&#xff01; …

2023-macOS下安装anaconda,终端自动会出现(base)字样,如何取消

2023-macOS下安装anaconda&#xff0c;终端自动会出现(base)字样&#xff0c;如何取消 安装后&#xff0c;我们再打开终端&#xff0c;就会自动出现了&#xff08;base&#xff09; 就会出现这样子的&#xff0c;让人头大&#xff0c; 所以我们要解决它 具体原因是 安装了anac…

从JDBD的封装方面重新认识Mybaits

前言&#xff1a;SQLSession是对JDBC的封装 MyBatis 是一个 Java 持久化框架&#xff0c;它通过对 JDBC 的封装来简化数据库访问操作。核心的 SQLSession 对象是 MyBatis 的核心组件之一&#xff0c;负责管理数据库连接、执行 SQL 语句以及映射查询结果等功能。 具体来说&…

Windows11恢复组策略编辑器功能的方法

原因分析 日常工作学习中,对 Windows 计算机上的问题进行故障排除时,有些高级用户经常使用组策略编辑器轻松修复它。通过其分层结构,您可以快速调整应用于用户或计算机的设置。如果搜索结果中缺少组策略编辑器,则可能必须使用注册表编辑器作为疑难解答工具,这是一种更复杂…

力扣:147. 对链表进行插入排序(Python3)

题目&#xff1a; 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤: 插入排序是迭代的&#xff0c;每次只移动一个元素&#xff0c;直到所有元素可以形成一个有序的输出列表。每次迭代中&#xff0c…

0基础学习PyFlink——时间滑动窗口(Sliding Time Windows)

在《0基础学习PyFlink——时间滚动窗口(Tumbling Time Windows)》我们介绍了不会有重复数据的时间滚动窗口。本节我们将介绍存在重复计算数据的时间滑动窗口。 关于滑动窗口&#xff0c;可以先看下《0基础学习PyFlink——个数滑动窗口&#xff08;Sliding Count Windows&#x…

韩国ORANGE橘子室内高尔夫—万万没想到除了打球还能唱歌、看电影

韩国ORANGE橘子室内高尔夫—万万没想到除了打球还能唱歌、看电影&#xff01; 你知道吗&#xff1f;室内高尔夫除了打球&#xff0c;还可以唱歌 看电影&#xff01; 在这个让人兴奋的室内高尔夫场地&#xff0c;你将体验到全新的娱乐方式。快来和小伙伴们一起挥杆&#xff0c;…

springboot医院绩效考核系统源码

医院绩效考核系统是一种以人力资源管理为基础&#xff0c;选用适合医院组织机构属性的绩效理论和方法&#xff0c;基于医院战略目标&#xff0c;构建全方位的绩效考评体系&#xff0c;在科学、合理的绩效管理体系基础上&#xff0c;采用科学管理的方法&#xff0c;如平衡计分卡…

DNEAT: 一个全新的用于OD需求预测的动态节点-边注意力网络

文章信息 本周阅读的论文是一篇2020年发表在《Transportation Research Part C》上关于OD需求预测的文章&#xff0c;题目为《DNEAT: A novel dynamic node-edge attention network for origin-destination demand prediction》。 摘要 近年来&#xff0c;网约车服务平台在全球…

MCU HardFault_Handler调试方法

一.获取内核寄存器的值 1.在MDK的DEBUG模式下&#xff0c;当程序出现跑飞后&#xff0c;确定卡死在HardFault_Handler中断处 2. 通过Register窗口读取LR寄存器的值来确定当前系统使用堆栈是MSP还是PSP LR寄存器值堆栈寄存器0xFFFFFFF9MSP寄存器0xFFFFFFFDPSP寄存器 如下图所…

机器学习-特征工程

一、特征工程介绍 1.1 什么是特征 数值特征&#xff08;连续特征&#xff09;、文本特征&#xff08;离散特征&#xff09; 1.2 特征的种类 1.3 特征工程 特征是机器学习可疑直接使用的&#xff0c;模型和特征之间是一个循环过程&#xff1b; 实际上特征工程就是将原始数据…

在Photoshop中如何校正倾斜的图片

在Photoshop中如何校正倾斜的图片呢&#xff1f;今天就教大家如何操作。 将需要操作的图片拉到PS软件中&#xff0c;自动形成项目。 点击上方“滤镜”中的“镜头校正”。 进入“镜头校正”窗口&#xff0c;点击左侧的“拉直工具”。文章源自设计学徒自学网-http://www.sx1c.co…

Docker(1)——安装Docker以及配置阿里云镜像加速

目录 一、简介 二、安装Docker 1. 访问Docker官网 2. 卸载旧版本Dokcer 3. 下载yum-utils&#xff08;yum工具包集合&#xff09; 4. 设置国内镜像仓库 5. 更新yum软件包索引 6. 安装Docker 7. 启动Docker 8. 卸载Docker 三、阿里云镜像加速 1. 访问阿里云官网 2. …

ElasticSearch快速入门实战

全文检索 什么是全文检索 全文检索是一种通过对文本内容进行全面索引和搜索的技术。它可以快速地在大量文本数据中查找包含特定关键词或短语的文档&#xff0c;并返回相关的搜索结果。全文检索广泛应用于各种信息管理系统和应用中&#xff0c;如搜索引擎、文档管理系统、电子…

Scan2BIM实战:从3D扫描到BIM模型生成

最近&#xff0c;我被问过很多次这个问题&#xff0c;所以我想我会尽力传达答案。 我应该指出&#xff0c;以下是概述&#xff0c;而不是非常详细的分步过程。 有很多因素会决定这项工作&#xff1b; 详细程度、扫描设备、点云配准软件和 CAD 软件等。 由于不知道你可能拥有或感…

netty实战-手写通信框架

通信框架功能设计 功能描述 通信框架承载了业务内部各模块之间的消息交互和服务调用&#xff0c;它的主要功能如下&#xff1a; 基于 Netty 的 NIO 通信框架&#xff0c;提供高性能的异步通信能力&#xff1b; 提供消息的编解码框架&#xff0c;可以实现 POJO 的序列化和反序…