[Spring Cloud] (6)gateway整体加解密

server/2024/9/23 9:25:42/

文章目录

  • 简述
  • 整体效果
  • 后端
    • 增加配置
      • nacos增加配置
      • GlobalConfig
    • 添加请求整体解密拦截器DecryptionFilter
    • 添加响应整体解密拦截器EncryptionFilter
  • 前端
    • 请求拦截器
      • 添加整体加密逻辑
      • 请求头中添加sessionId
    • 响应拦截器
      • 添加整体解密逻辑

简述

本文网关gateway微服务,vue已开源到gitee
杉极简/gateway网关阶段学习

在经历前面5章的铺垫下,终于进入到了最重要的环节——拦截器功能实现,此时我们需要看的东西就比较集中而且简单了。

在Java分布式系统中,使用Spring Cloud Gateway进行整体的加解密处理可以起到以下几个关键作用:

  1. 安全性增强:通过加密客户端与服务端之间传输的数据,可以防止敏感信息在网络传输过程中被截获和窃取。即使数据包被拦截,没有相应的密钥也无法解读数据内容,从而保护了用户隐私和系统安全。
  2. 数据完整性校验:在数据传输过程中,除了加密保护数据外,还可以通过签名机制来确保数据的完整性。签名可以验证数据在传输过程中是否被篡改,增强了数据的可靠性。
  3. 减轻后端服务负担:通过在网关层统一进行加解密处理,后端微服务不需要再集成额外的安全模块来进行数据的加解密工作,从而降低了后端服务的复杂性和资源消耗。
  4. 统一的安全管理:网关作为所有请求的入口点,可以实现统一的安全管理策略。所有的加解密操作按照统一的规则进行,便于管理和维护,同时方便对安全策略进行升级和维护。
  5. 提高开发效率:开发者可以专注于业务逻辑的实现,而不必关注数据传输的安全细节,提高了开发效率和项目的迭代速度。
  6. 跨服务数据保护:在微服务架构中,服务间可能需要相互调用和数据交换。网关加解密确保了即使在内部服务间传输的数据也是安全的,防止了潜在的内部安全风险。
  7. 应对合规性要求:某些行业或地区的法律法规要求对用户数据进行加密处理,使用网关加解密可以更好地符合这些合规性要求,避免法律风险。
  8. 灵活的策略调整:网关层的加解密策略可以根据业务需求灵活调整,比如可以针对不同的请求路径或请求方法应用不同的加密算法和密钥,而不需要修改每个微服务的代码。
  9. 减少敏感信息泄露风险:对于某些对数据安全性要求极高的应用场景,如金融交易、个人隐私信息等,网关加解密可以有效减少敏感信息泄露的风险。
  10. 提高系统的可扩展性:随着业务的发展,系统的安全需求可能会变化。在网关层实现加解密可以方便地根据新的安全需求进行扩展和更新,而不影响现有的业务流程和后端服务。

Spring Cloud Gateway的加解密功能为分布式系统提供了一个安全、高效、灵活的数据传输解决方案,有助于提升系统的安全性和开发效率。

整体效果

像不需要加密的接口,请求参数与响应数据都是未加密的
image.png
image.png
像数据接口,发送的参数是加密数据,接收到的都是是加密数据。但是经过前后端的拦截器处理之后,显示出的依然是正常数据
image.png
image.png

后端

增加配置

nacos增加配置

  # 整体对称加解密aes: true# 整体非对称加解密rsa: true

GlobalConfig

java">    /*** 整体对称加解密*/private boolean aes;/*** 整体非对称加解密*/private boolean rsa;

image.png

添加请求整体解密拦截器DecryptionFilter

java">package com.fir.gateway.filter.request;import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;;/*** 请求整理解密-请求拦截器** @author fir*/
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic int getOrder() {return -280;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("整体解密:start");ServerHttpRequest req = exchange.getRequest();String method = req.getMethodValue();boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa || aes) {// 白名单路由判断ServerHttpRequest request = exchange.getRequest();String path = request.getPath().toString();List<String> whiteUrls = globalConfig.getWhiteUrls();if(!whiteUrls.contains(path)){ServerHttpRequest builder = req.mutate().build();if (HttpMethod.GET.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);} else if (HttpMethod.POST.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);}exchange = exchange.mutate().request(builder).build();log.info("整体解密:true");}else {log.info("整体解密:true,白名单");}}else {log.info("整体解密:true,验证已关闭");}return chain.filter(exchange);}/*** 获取请求参数等信息进行过滤处理** @param exchange          请求* @param serverHttpRequest 请求* @return 处理结束的参数*/@SneakyThrowsprivate ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {String session = exchange.getRequest().getHeaders().getFirst("s");if (session == null) {throw new CustomException(AjaxStatus.SESSION_INVALID);}JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);if (jsonObject == null) {throw new CustomException(AjaxStatus.SESSION_EXPIRE);}ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);String privateKey = connectDTO.getPrivateKey();String secretKey = connectDTO.getSecretKey();// 获取原参数URI uri = serverHttpRequest.getURI();String originalQuery = uri.getRawQuery();String decodedQuery = null;if(StringUtils.isNotBlank(originalQuery)){decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");}// 更改参数MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {// 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。// 除此以外的所有方式不能通过本方法进行解密String[] array = decodedQuery.split("=");if (array.length > 1) {decodedQuery = array[1];if (decodedQuery != null) {boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa) {// 对数据进行非对称解密originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);}if (aes) {// 对数据进行对称解密originalQuery = AESUtils.decrypt(originalQuery, secretKey);}}Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);if (dataMap != null) {Set<String> strings = dataMap.keySet();for (String key : strings) {String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());newQueryParams.add(key, encodedString);}}}}// 替换查询参数URI newUri = UriComponentsBuilder.fromUri(uri).query(null).queryParams(newQueryParams).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();// 将解密后的参数重新设置到请求中uri = request.getURI();log.info("更改后的当前请求参数为: {}", uri.getRawQuery());return request;}
}

添加响应整体解密拦截器EncryptionFilter

java">package com.fir.gateway.filter.request;import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;;/*** 请求整理解密-请求拦截器** @author fir*/
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic int getOrder() {return -280;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("整体解密:start");ServerHttpRequest req = exchange.getRequest();String method = req.getMethodValue();boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa || aes) {// 白名单路由判断ServerHttpRequest request = exchange.getRequest();String path = request.getPath().toString();List<String> whiteUrls = globalConfig.getWhiteUrls();if(!whiteUrls.contains(path)){ServerHttpRequest builder = req.mutate().build();if (HttpMethod.GET.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);} else if (HttpMethod.POST.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);}exchange = exchange.mutate().request(builder).build();log.info("整体解密:true");}else {log.info("整体解密:true,白名单");}}else {log.info("整体解密:true,验证已关闭");}return chain.filter(exchange);}/*** 获取请求参数等信息进行过滤处理** @param exchange          请求* @param serverHttpRequest 请求* @return 处理结束的参数*/@SneakyThrowsprivate ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {String session = exchange.getRequest().getHeaders().getFirst("s");if (session == null) {throw new CustomException(AjaxStatus.SESSION_INVALID);}JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);if (jsonObject == null) {throw new CustomException(AjaxStatus.SESSION_EXPIRE);}ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);String privateKey = connectDTO.getPrivateKey();String secretKey = connectDTO.getSecretKey();// 获取原参数URI uri = serverHttpRequest.getURI();String originalQuery = uri.getRawQuery();String decodedQuery = null;if(StringUtils.isNotBlank(originalQuery)){decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");}// 更改参数MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {// 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。// 除此以外的所有方式不能通过本方法进行解密String[] array = decodedQuery.split("=");if (array.length > 1) {decodedQuery = array[1];if (decodedQuery != null) {boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa) {// 对数据进行非对称解密originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);}if (aes) {// 对数据进行对称解密originalQuery = AESUtils.decrypt(originalQuery, secretKey);}}Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);if (dataMap != null) {Set<String> strings = dataMap.keySet();for (String key : strings) {String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());newQueryParams.add(key, encodedString);}}}}// 替换查询参数URI newUri = UriComponentsBuilder.fromUri(uri).query(null).queryParams(newQueryParams).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();// 将解密后的参数重新设置到请求中uri = request.getURI();log.info("更改后的当前请求参数为: {}", uri.getRawQuery());return request;}
}

前端

请求拦截器

添加整体加密逻辑

javascript">// 请求整体加密
if (AESKey) {const secretKey = this.get("secretKey");let date = JSON.stringify(request.params);date = this.encryptAES(date, secretKey);request.params = {"data": date};
}
if (AESKey && RSAKey) {const serverPublicKey = this.get("serverPublicKey");let date = request.params.data;date = this.rsaEncrypt(date, serverPublicKey);request.params = {"data": date};
} else if (RSAKey) {const serverPublicKey = this.get("serverPublicKey");let date = JSON.stringify(request.params);date = this.rsaEncrypt(date, serverPublicKey);request.params = {"data": date};
}

image.png

请求头中添加sessionId

javascript">            let s = this.get("sessionId")// 请求中增加会话信息if (s) {request.headers.s = s;}

image.png

响应拦截器

添加整体解密逻辑

修改一下内容:

javascript">return securityUtils.gatewayResponse(response);

image.png

securityUtils.js中添加响应处理函数

javascript">/*** gateway网关验证信息处理(响应头)*/
gatewayResponse(response) {let key = true;// 放置业务逻辑代码// response是服务器端返回来的数据信息,与Promise获得数据一致let data = response.data// config包含请求信息let config = response.config// 判断 data 是否为对象if (typeof data === 'object' && data !== null) {// 判断 data 是否匹配特定格式if (Object.prototype.hasOwnProperty.call(data, 'msg') &&Object.prototype.hasOwnProperty.call(data, 'code') &&typeof data.msg === 'string' &&typeof data.code === 'number') {// 数据匹配特定格式if (data.code === 401) {sessionStorage.clear()}return data;}}// 获取当前请求的urllet url = config.urlwhiteList.find(function (value) {if (value === url) {key = false;}});// 对非白名单数据进行整体解密处理if (key) {// 获取加密密钥,并传入解密组件进行解密if (RSAKey) {const privateKey = this.get("privateKey")data = this.rsaDecrypt(data, privateKey)}if (AESKey) {let securityKey = this.get("secretKey")data = this.decryptAES(data, securityKey)}if (data != null && data !== "") {data = JSON.parse(data);}else {data =  new Promise(() => {});}}return data;
},

image.png


http://www.ppmy.cn/server/34175.html

相关文章

编译链接过程

编译链接过程 预编译阶段 删除所有的“#define”&#xff0c;并且展开所有的宏定义; 处理所有的条件预编译指令&#xff0c;“#if”、“#ifdef”、“#endif”等; 处理“#include”预编译指令&#xff0c;将被包含的文件插入到该预编译指令的位置; 删除所有的注释; 添加行号和文…

如何面对并发下的bug

整理总结自蒋炎岩老师的b站课程&#xff0c;https://jyywiki.cn/OS/2022/index.html 并发bug与应对 应对bug的方法 在代码里边增加很多检查(加断言) #include "thread.h"unsigned long balance 100;void Alipay_withdraw(int amt) {if (balance > amt) {usleep(…

上位机图像处理和嵌入式模块部署(树莓派4b和mcu的分工与配合)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 树莓派4b从广义上来说&#xff0c;它本身就是一个mini小电脑。我们需要什么软件&#xff0c;就可以apt install去下载即可。这和之前嵌入式linux开…

AI新突破:多标签预测技术助力语言模型提速3倍

DeepVisionary 每日深度学习前沿科技推送&顶会论文分享&#xff0c;与你一起了解前沿深度学习信息&#xff01; 引言&#xff1a;多标签预测的新视角 在人工智能领域&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;预测模型的训练方法一直在…

[微信小程序] 入门笔记1-滚动视图组件

[微信小程序] 入门笔记1-滚动视图组件 1.页面&组件&渲染 在小程序是由一个个页面page组成, 而页面又是由一个个组件component组成.和网页类似,这里的组件指的就是输入框<input>,按钮<button>,文本<text>,图片<image>等元素.如果你学过网页一…

RK3568 学习笔记 : 精简 u-boot env 默认复杂的多种引导启动设置

前言 环境&#xff1a; 正点原子 Atompi-CA1 RK3568 开发板、正点原子 DLRK3568 开发板&#xff0c;&#xff08;一时脑热买了两块 RK3568 开发板&#xff09;&#xff0c;Atompi-CA1 RK3568 开发板比较小巧&#xff0c;利于一些前期的嵌入式 Linux 开发学习与实践。 RK3568 开…

以太网基础-IP、ICMP、ARP协议

一、IP协议 参考&#xff1a;rfc791.txt.pdf (rfc-editor.org) IP协议&#xff08;Internet Protocol&#xff09;是TCP/IP协议族中最核心的协议&#xff0c;提供不可靠的、无连接的、尽力而为的数据报传输服务。 IP报文数据头如下 Version&#xff1a;4bit&#xff0c;4表示…

小白入门 CSS 语法规则

文章导读&#xff1a;AI 辅助学习前端&#xff0c;包含入门、进阶、高级部分前端系列内容&#xff0c;当前是 CSS 的部分&#xff0c;瑶琴会持续更新&#xff0c;适合零基础的朋友&#xff0c;已有前端工作经验的可以不看&#xff0c;也可以当作基础知识回顾。 这篇文章给大家…