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 {

    @Resource
    private SecureProperties secureProperties;

    private SecureComponent secureComponent;

    @Override
    public 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) {
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
                httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
                return httpHeaders;
            }

            @Override
            public 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) {
            @Override
            public 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);
            }

            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }

            @Override
            public 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);

    @Override
    public 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-server2
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/server/list/server2/**

# 自定义配置
feng:
  gateway:
    secure:
      request-switch:
        enable: false
      response-switch:
        enable: true
      algorithm: 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();


    @PostConstruct
    default 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));

    @Override
    public String encrypt(String originalText) {
        return AES_INSTANCE.encryptHex(originalText);
    }

    @Override
    public String decrypt(String encryptedText) {
        return AES_INSTANCE.decryptStr(encryptedText);
    }

    @Override
    public 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 {

    @Resource
    private SecureProperties secureProperties;

    public static void main(String[] args) {
        SpringApplication.run(FengGatewayApplication.class, args);
    }

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

跨国文件传输为什么要用专业的大文件传输软件?

跨国文件传输是许多跨国企业需要的基础工作&#xff0c;对于传输的质量和速度要求也是很严格的&#xff0c;随着数据量的不断增加&#xff0c;寻常传统的传输方式肯定是不行&#xff0c;需要新的技术和方式来进行传输&#xff0c;大文件传输软件应运而出&#xff0c;那它有什么…

软考中项集成如何画图?计算题怎么考的?

2023下半年软考集成一共考6个批次&#xff0c;10月28日、29日软考集成考了第一、二、三、四批次&#xff0c;11月4日软考集成再考第五批和第六批。 先说一下通过10.28-29得出的软考机考注意事项&#xff1a; 1、草稿纸不能自带&#xff0c;考试现场会发放草稿纸&#xff0c;草…

钡铼技术ARM工控机在机器人控制领域的应用

ARM工控机是一种基于ARM架构的工业控制计算机&#xff0c;用于在工业自动化领域中进行数据采集、监控、控制和通信等应用。ARM&#xff08;Advanced RISC Machine&#xff09;架构是一种低功耗、高性能的处理器架构&#xff0c;广泛应用于移动设备、嵌入式系统和物联网等领域。…

关于pytorch张量维度转换及张量运算

关于pytorch张量维度转换大全 1 tensor.view()2 tensor.reshape()3 tensor.squeeze()和tensor.unsqueeze()3.1 tensor.squeeze() 降维3.2 tensor.unsqueeze(idx)升维 4 tensor.permute()5 torch.cat([a,b],dim)6 torch.stack()7 torch.chunk()和torch.split()8 与tensor相乘运算…

预处理详解(一)

1 预定义符号 __FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C&#xff0c;其值为1&#xff0c;否则未定义 这些预定义符号都是…

Linux纯C串口开发

为什么要用纯C语言 为了数据流动加速&#xff0c;实现低配CPU建立高速数据流而不用CPU干预&#xff0c;避免串口数据流多次反复上升到软件应用层又下降低到硬件协议层。 关于termios.h 麻烦的是&#xff0c;在 Linux 中使用串口并不是一件最简单的事情。在处理 termios.h 标头…

怎么检测开关电源质量的好坏?测试的方法是什么?

开关电源的工作原理 开关电源(简称SMPS)是常见的一种电源供应器&#xff0c;是高频化的电能转换装置&#xff0c;可以将电压透过不同形式的架构转换为用户端所需求的电压或电流。具有体积小、功耗小、效率高、高可靠性的特点&#xff0c;被广泛应用在工业、军工设备、科研设备、…

机器学习---支持向量机的初步理解

1. SVM的经典解释 改编自支持向量机解释得很好 |字节大小生物学 (bytesizebio.net) 话说&#xff0c;在遥远的从前&#xff0c;有一只贪玩爱搞破坏的妖怪阿布劫持了善良美丽的女主小美&#xff0c;智勇双全 的男主大壮挺身而出&#xff0c;大壮跟随阿布来到了妖怪的住处&…

Docker compose容器编排

Docker compose容器编排 1、Docker compose简介 docker-compose是docker的编排工具&#xff0c;用于定义和运行一个项目&#xff0c;该项目包含多个docker容器&#xff0c;在如今的微服务时代&#xff0c;一个项目会存在多个服务&#xff0c;使用docker一个个部署操作的话就会…

React中的状态管理

目录 前言 1. React中的状态管理 1.1 本地状态管理 1.2 全局状态管理 Redux React Context 2. React状态管理的优势 总结 前言 当谈到前端开发中的状态管理时&#xff0c;React是一个备受推崇的选择。React的状态管理机制被广泛应用于构建大型、复杂的应用程序&#xf…

计算机毕业设计选题推荐-超市售货微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

注册中心ZK、nameServer、eureka、Nacos介绍与对比

前言 注册中心的由来 微服务架构是存在着很多跨服务调用,每个服务都存在着多个节点,如果有多个提供者和消费者,当提供者增加/减少或者消费者增加/减少,双方都需要感知发现。所以诞生了注册中心这个中间件。 市面上有很多注册中心,如 Zookeeper、NameServer、Eureka、Na…

OpenCV 笔记(4):图像的算术运算、逻辑运算

Part11. 图像的算术运算 图像的本质是一个矩阵&#xff0c;所以可以对它进行一些常见的算术运算&#xff0c;例如加、减、乘、除、平方根、对数、绝对值等等。除此之外&#xff0c;还可以对图像进行逻辑运算和几何变换。 我们先从简单的图像加、减、逻辑运算开始介绍。后续会有…

企业 Tomcat 运维 部署tomcat反向代理集群

一、Tomcat 简介 Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c; Tomcat和Nginx、Apache(httpd)、Web服务器一样&#xff0c;具有处理HTML页面的功能不过Tomcat处理静态HTML的能力不如Nginx/Apache服务器 一个tomcat默认并…

ip划分与私公网ip、ip的传递

报文问路&#xff1a;1、不知道跳转默认路由器&#xff0c;2、知道路径&#xff0c;向对应路径发出报文&#xff0c;3、路口路由器&#xff0c;下一步就是目标主机在哪。 想要通信必须同在一个局域网&#xff0c;其实将公网就可以看作一个大型的局域网。 在同一个局域网内发送…

61. 旋转链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;李歘歘的博客 &#x1f3c6; &#x1f33a;每天不定期分享一些包括但不限于计算机基础、算法、后端开发相关的知识点&#xff0c;以及职场小菜鸡的生活。&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&am…

恶意软件防范和拦截: 提供防范恶意软件攻击的策略

恶意软件&#xff0c;或者俗称的“病毒”&#xff0c;一直是IT领域的一个严重威胁。这些恶意软件可以窃取敏感信息、损害系统稳定性&#xff0c;甚至对企业和个人造成重大经济损失。在这篇博客文章中&#xff0c;我们将讨论如何防范和拦截恶意软件攻击&#xff0c;包括使用反病…

【unity/vufornia】Duplicate virtual buttons with name.../同一个ImageTarget上多个按钮失灵

问题&#xff1a;在同一个ImageTarget上添加多个按钮时无法触发对应按钮的事件。 解决过程&#xff1a; 1.查看报错&#xff1a;“Duplicate virtual buttons with name...”这一行&#xff0c;顾名思义&#xff0c;命名重复。 2.英文搜索到以下文章&#xff0c;应该在inspe…

算法通过村第十八关-回溯|青铜笔记|什么叫回溯(中篇)

文章目录 前言回溯的核心问题撤销操作解释总结 前言 提示&#xff1a;阳光好的时候&#xff0c;会感觉还可以活很久&#xff0c;甚至可以活出喜悦。 --余秀华 回溯是非常重要的算法思想之一&#xff0c;主要解决一些暴力枚举也搞不定的问题&#xff08;这里埋个坑&#x1f4a3;…

Kubernetes 概述以及Kubernetes 集群架构与组件

目录 Kubernetes概述 K8S 是什么 为什么要用 K8S K8S 的特性 Kubernetes 集群架构与组件 核心组件 Master 组件 Node 组件 ​编辑 Kubernetes 核心概念 常见的K8S按照部署方式 Kubernetes概述 K8S 是什么 K8S 的全称为 Kubernetes,Kubernetes 是一个可移植、可扩…