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.rhkb.cn/news/177097.html

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

相关文章

Python基础入门例程27-NP27 朋友们的喜好(列表)

最近的博文&#xff1a; Python基础入门例程26-NP26 牛牛的反转列表&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程25-NP25 有序的列表&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程24-NP24 淘汰排名最后的学生&#xff08;列表&#xff09;-CSDN博客…

2017年第三届 美亚杯电子取证 个人赛题解

记录做题个人赛题目 取证大师直接取证 1 Gary的笔记本电脑已成功取证并制作成镜像 (Forensic Image)&#xff0c;下列哪个是其MD5哈希值。 A.0CFB3A0BB016165F1BDEB87EE9F710C9 B.5F1BDEB87EE9F710C90CFB3A0BB01616 C.A0BB016160CFB3A0BB0161661670CFB3 D.16160CFB3A0BB0161…

如何在MacOS使用homebrew安装Nginx

文章目录 Homebrew安装nginxbrew启动Nginxbrew关闭Nginx 参考文章地址 Homebrew安装nginx 在确保MacOS 安装Homebrew成功以后&#xff0c;执行如下命令 brew install nginx注意&#xff1a;Nginx安装成功后会提示目录位置&#xff1b;每个人的系统可能因为Homebrew的安装位置…

【Java数据结构重点知识】第三节:认识包装类和泛型

目录 一&#xff1a;包装类 1.什么是包装类 2.装箱和拆箱 3.一个经典面试题 二&#xff1a;泛型 1.什么是泛型 2.语法 3.使用 4.泛型是如何编译的 5.泛型的上界 一&#xff1a;包装类 1.什么是包装类 &#xff08;1&#xff09;概念&#xff1a;在Java中&#xff0…

在Win10系统进行MySQL的安装、连接、卸载

在Win10系统进行MySQL的安装、连接、卸载 MySQL的安装 本教程在Win10系统下安装部署MySQL-8.0.32版。 MySQL安装参考地址 MySQL安装包地址 提取码: rnbc。 选择下载mysql-installer-community-8.0.32.0安装包。 连接数据库 方式一&#xff1a; 安装后&#xff0c;可以在开始…

Test-Agent----基于Centos7系统部署Test-Agent

【Test-Agent----基于Centos7系统部署Test-Agent】 一、部署 Test-Agent 1.1 环境准备 环境基本信息&#xff1a;Centos7.9操作系统&#xff0c;16核64G1T硬盘&#xff0c;Python3.9.7 1.2 部署 Test-Agent &#xff08;1&#xff09;安装git-lfs cd /opt curl -s https:…

Agent 应用于提示工程

如果Agent模仿了人类在现实世界中的操作方式&#xff0c;那么&#xff0c;能否应用于提示工程即Prompt Engingeering 呢&#xff1f; 从LLM到Prompt Engineering 大型语言模型(LLM)是一种基于Transformer的模型&#xff0c;已经在一个巨大的语料库或文本数据集上进行了训练&…

钡铼技术助力ARM工控机在智慧交通中的创新应用

在交通运输领域&#xff0c;钡铼技术ARM工控机可以实现以下功能&#xff1a; 实时监控和管理&#xff1a;利用钡铼技术ARM工控机&#xff0c;可以对交通运输中的车辆、船只、飞机等进行实时监测和管理&#xff0c;帮助调度员提高车辆调度和路线规划的准确性和效率。 安全保障&…

不要在不使用cvx优化的场合使用log_det,应改为log(det(..)),否者可能会出现奇怪的错误

跑代码的时候遇到的问题 %% 分解为功率和单位模&#xff0c;交替优化功率分量和单位模clc;clear; Nt 8; % 发射天线数目 8 Nr 4; % 接收天线数目 4 Ne 6; % Eve天线数目 6 noisePower 1; SNRTotaldB -5:5:35; %%%%最大发射功率单位dB SNRTotal 10.^(SNRTotaldB./…

【Liunx应用市场】yum

【Liunx应用市场】yum 1. Linux 软件包管理器 yum2. yum源3. yum的使用3.1 yum查找3.2 yum安装3.3 yum删除 所属专栏&#xff1a;Linux学习❤️ &#x1f680; >博主首页&#xff1a;初阳785❤️ &#x1f680; >代码托管&#xff1a;chuyang785❤️ &#x1f680; >感…

数据标准是什么?如何建立企业的数据标准?

2023年10月25日国家数据局正式揭牌&#xff0c;由国家发展和改革委员会管理。国家数据局的主要职责是负责协调推进数据基础制度建设&#xff0c;统筹数据资源整合共享和开发利用&#xff0c;统筹推进数字中国、数字经济、数字社会规划和建设等。国家越来越重视数据资源的价值&a…

Spring MVC的常用注解(设置响应篇)

目录 1.返回静态页面 2.返回数据 3.返回HTML代码片段 4.返回json 5.设置状态码 6.设置Header &#xff08;1&#xff09;.设置 Content-Type &#xff08;2&#xff09;.设置其他Header 推荐先看前篇博客Spring MVC的常用注解&#xff08;接收请求数据篇&#xff09; 接收…

微信小程序怎么制作?【小程序开发平台教学】

随着移动互联网的快速发展&#xff0c;微信小程序已经成为了人们日常生活中不可或缺的一部分。从购物、支付、出行到社交、娱乐、教育&#xff0c;小程序几乎涵盖了我们生活的方方面面。那么&#xff0c;对于有营销需求的企业商家来说&#xff0c;如何制作一个自己的微信小程序…

微服务初始和Nacos安装

一)初始微服务: 微服务是将一个大型的&#xff0c;单一的应用程序拆分成多个小型服务&#xff0c;每一个服务负责于特定的业务功能&#xff0c;并且可以通过网络来和其他服务进行通讯&#xff0c;是一个思想&#xff0c;将一个大的项目拆分成多个小的项目&#xff0c;多个小的项…

DDACO

算法 alternative set包括备用路径和禁忌表&#xff0c;roulette体现所占比例越大被选中概率越高的思想。在图2中&#xff0c;节点表示与图1中的边相同的路径&#xff0c;边表示邻接关系&#xff08;AND关系&#xff09; 所有的帕累托最优解构成帕累托最优解集 作者未提供代码…

2023年11月1日蜻蜓C影视追剧系统v1.2.2更新-与时俱进调整微信登录授权获取方式-修复无法登陆授权

2023年11月1日蜻蜓C影视追剧系统v1.2.2更新-与时俱进调整微信登录授权获取方式-修复无法登陆授权 问题背景&#xff1a; 小程序用户头像昵称获取规则调整公告官方 微信团队2022-05-09 更新时间&#xff1a;2022年11月9日 由于 PC/macOS 平台「头像昵称填写能力」存在兼容性问…

【移远QuecPython】EC800M物联网开发板的UART串口配置

【移远QuecPython】EC800M物联网开发板的UART串口配置 文章目录 UART初始化UART读写写数据轮询读取中断读取 串口测试附录&#xff1a;列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 UART初始化 UART库&#xff1a; from machine import UART引脚和UART对应…

请问嵌入式行业会变得和java一样卷死吗?

今日话题&#xff0c;嵌入式行业会变得和java一样卷死吗&#xff1f;嵌入式行业未来不太可能出现像互联网行业那样的内卷化情况。嵌入式开发在制造业等重要第二产业中扮演关键角色&#xff0c;这些行业的稳定性较高&#xff0c;波动较小&#xff0c;因此嵌入式岗位的稳定性相对…

讲述为什么要学习Adobe XD以及 Adobe XD下载安装

首先 我们要了解 Adobe XD 是个什么东西 XD是Adobe公司专门开发出来面向交互、界面设计的矢量绘图工具。 然后是 他可以做什么&#xff1f; 最基本的 可以做UI界面设置 所有 手机 平板 电脑等设备的UI界面 我们都可以通过XD完成 还有就是原型设置 我们可以做各种界面图 还有…