【springboot】springboot接口参数全局解密,解决request内容修改后如何重新设置回去的问题

news2024/11/15 18:55:53

文章目录

  • 核心思路
  • spring&servelt基础
  • 核心接口类
  • body解密核心原理讲解
  • get解密核心原理讲解

核心思路

  1. 拦截每次请求 所以要么在拦截器 要么在过滤器中做 (正常来说 其实只能在过滤器做)
  2. 修改request中的参数
  3. 把修改后的参数设置回去(难点)

spring&servelt基础

每个http请求都是访问了一个servlet,servlet有过滤器filter的概念。

在spring框架中 其实就是封装了一个DispatcherServlet,并且实现了各种过滤器和拦截器。

在过滤器中,有很重要的一个步骤:
传递过滤链,用通俗的话解释就是 当前过滤器的一顿操作 不能阻碍了其它过滤器的执行,所以需要将request和response传递给下一个过滤器

filterChain.doFilter(request, response);

所以我们在过滤器中进行解密,并将request的值修改后传递,这样可以保证每个过滤器拿到的都是解密后的值。

如果是在拦截器里面做,那可能在过滤器就出现了报错(取决于过滤器自定义的功能复不复杂 有没有涉及参数解密), 我们避免节外生枝 本文都是在过滤器里面进行解密。

核心接口类

  1. HttpServletRequestWrapper
    这里补充一个基础知识,流读了一遍之后就不能再读取了,所以需要通过这个包装类,使得流可以重复使用

  2. ServletInputStream
    与HttpServletRequestWrapper的子类配合使用 重写读取流的方法

  3. OncePerRequestFilter
    拦截每次请求的过滤器

(注:本文代码是demo 不推荐复制即用,最好需要有自己的理解)

@Configuration
public class DecryptRequestFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest  request, HttpServletResponse  response, FilterChain chain)
            throws IOException, ServletException {
        // 包装请求对象
        CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper(request);
        // 继续过滤链,使用包装后的请求对象
        chain.doFilter(wrappedRequest, response);
    }

}
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.catalina.util.ParameterMap;
import org.springframework.web.servlet.HandlerMapping;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private  byte[] body;
    private final Map<String, String[]> params = new HashMap<>();
    private final Map<String, String> pathVariables = new HashMap<>();

    public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取并缓存请求体内容
        String s = new String(readBytes(request.getInputStream()), StandardCharsets.UTF_8);
        // 这里模拟的body解密
        String replace = s.replace("孟秋", "大孟秋");
        body = replace.getBytes(StandardCharsets.UTF_8);

        ParameterMap<String, String[]> parameterMap = (ParameterMap<String, String[]>) request.getParameterMap();
        parameterMap.setLocked(false);
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
        	// 取值
            String[] value = entry.getValue();
            String s1 = value[0];
            
		    // 模拟解密
            parameterMap.put(entry.getKey(), new String[]{"解密参数"});
        }
        parameterMap.setLocked(true);
        this.params.putAll(decryptParams(parameterMap));
        // spring自带的是PathVariableMethodArgumentResolver类来处理 但是它不让重写
 // PathVariables 解密暂时还没做 这里只是取值了, 暂没考虑如何解密并设置回去
        decryptPathVariables((Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
    }

    private byte[] readBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, length);
        }
        return byteArrayOutputStream.toByteArray();
    }


    private Map<String, String[]> decryptParams(Map<String, String[]> parameterMap) {
        // 解密逻辑
        return parameterMap; // 示例
    }

    private void decryptPathVariables(Map<String, String> pathVariables) {
        if (pathVariables == null) {
            return;
        }
        pathVariables.entrySet().forEach(
                item ->{
                    // 解密逻辑
                    this.pathVariables.put(item.getKey(),item.getValue()+"解密");
                }
        );
    }

    @Override
    public String getParameter(String name) {
        String[] paramArray = params.get(name);
        return paramArray != null && paramArray.length > 0 ? paramArray[0] : null;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return params;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] parameterValues = super.getParameterValues(name);
		// 模拟get请求(query请求 即form表单提交)的解密
        return new String[]{"加密"};
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        return super.getAttributeNames();
    }

    @Override
    public Object getAttribute(String name) {
        return super.getAttribute(name);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        String s = new String(body, StandardCharsets.UTF_8);
        System.out.println(s);
        return new CachedBodyServletInputStream(body);
    }

    @Override
    public String getRequestURI() {
        String uri = super.getRequestURI();
        for (Map.Entry<String, String> entry : pathVariables.entrySet()) {
            uri = uri.replace("{" + entry.getKey() + "}", entry.getValue());
        }
        return uri;
    }
}


body解密核心原理讲解

核心:

  1. request.getInputStream的流缓存起来
  2. 改写缓存流的内容 实现解密
  3. 重写了HttpServletRequestWrapper类的getInputStream方法 该方法就是获取我们缓存起来的流 这样在controller层 @RequestBody获取的就是我们的缓存流(解密后的流)对应的内容

get解密核心原理讲解

get比body难一些,踩的坑也不少

先将参数取出来
(代码片段截取的上文代码)

       ParameterMap<String, String[]> parameterMap = (ParameterMap<String, String[]>) request.getParameterMap();

如果这个时候我们直接遍历map 并重新put修改值 是会报错的

      for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
      		// 取值
            String[] value = entry.getValue();
            String s1 = value[0];
            
			 // 这里是模拟解密
            parameterMap.put(entry.getKey(), new String[]{"解密参数"});
        }

会报错不支持操作 not supportException,如果这时候放弃就大意了,我们跟进源码看看为什么抛异常:

在这里插入图片描述
在这里插入图片描述
可以看到 如果locked 则会抛出异常,而它又提供了一个public的setLocked的类
在这里插入图片描述
所以这就是为什么代码中 会有这一行

   parameterMap.setLocked(false);

我们先把锁打开后,往map里面修改了内容,还需要把锁关上, 我们看ParameterMap类的源码也能看到 它有很多地方是通过判断是否锁了 我们不能改变原来的逻辑。

	// 修改完map后重新上锁
   parameterMap.setLocked(true);

如果我们的controller是 :

@GetMapping
public void test(Request request)

那么直接request.getParameterMap就可以获取解密后的参数了

众所周知 我们都用了spring/springboot 早就不太可能这么写丑陋代码了,更多时候是如下:

@GetMapping
public void test(String name)

这个时候会发现 明明reqeust的parameterMap被修改了,但是name参数竟然还是原始的值!
这个时候就需要打个断点调试了,博主经过调试后发现 最后是动态代理传的参改变了,servlet到controller层的过程中 虽然一开始确实是传的reqeust 但在某一步时 它去获取了parameterValues !

parameterMap变量位于org.apache.catalina.connector.Request类

而parameterValues变量位于org.apache.tomcat.util.http.Parameters类

(上面两个源码类都是基于springboot3版本自带的tomcat包,springboot2可能会有差异,博主推测都可以从RequestFacade类作为入口看)

所以我们需要在HttpServletRequestWrapper的子类 CustomHttpServletRequestWrapper 中重写parameterValues方法

(避免数据不一致情况下 我们map和values方法都将解密数据放进去)

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

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

相关文章

RegFormer:用于大规模点云配准的高效投影感知Transformer网络

目录 一、导言 二、相关工作 1、点云配准工作 2、大规模点云配准 3、Transformer引入配准工作 三、RegFormer 1、柱面投影 2、特征提取Transformer 3、双射关联Transformer(BAT) 4、刚性变换估计 5、损失函数 四、实验 一、导言 该论文来自于ICCV2023&#xff08;…

如何搞定聊天记录找回?三款数据恢复工具分享

聊天记录丢了怎么办&#xff1f;别急&#xff0c;我这就带大家看看市面上比较火的三款数据恢复软件在恢复聊天记录方面的表现如何。首先&#xff0c;我们得知道&#xff0c;聊天记录这东西&#xff0c;一旦误删&#xff0c;那可是心急如焚啊。所以&#xff0c;选择一款靠谱的数…

场外个股期权可以分批建仓吗?

场外个股期权的优势是可以进行风险的精细化管理&#xff0c;但由于期权价格变化的非线性特性&#xff0c;盈利与标的行情走势的相关性不断变化&#xff0c;场外个股期权最便宜的可以用2-5万买到100万市值的股票持仓一个月&#xff0c;下面是整理得出的场外个股期权可以分批建仓…

SwiftUI 6.0(iOS 18)监听滚动视图视口中子视图可见性的极简方法

概览 在 SwiftUI 的应用开发中,我们有时需要监听滚动视图中子视图当前的显示状态:它们现在是被滚动到可见视口(Viewport)?或仍然是隐藏在“未知的黑暗”中呢? 在 SwiftUI 早期版本中为了得偿所愿,我们需要借助一些“取巧”的手段。不过,从 SwiftUI 6.0(iOS 18)开始情…

echarts柱状图使用自定义图片填充柱体

这是我的柱状图图片纹理的实现过程的流水账式记录 方法一&#xff1a; 在option.series里面&#xff0c;给每一项配置上图片路径 let Image2 new Image() Image2.src src/assets/image/BarChart-line.pngcolor: {image: Image,repeat: repeat } 出来的效果比较适合整体纹样…

[Linux]如何在Ubuntu中安装Docker,并且学习基本操作?

一、我们为什么需要Docker&#xff1f; 相信大家都遇到过部署一个应用时缺少依赖的情况&#xff0c;往往我们需要手动解决依赖问题&#xff0c;在解决了依赖问题以后&#xff0c;好不容易安装了这个应用&#xff0c;但是我们更换了计算机以后又需要重复上面步骤将这个软件再安装…

Unity(2022.3.38LTS) - Project Settings详细介绍不看你就亏大了(一)

目录 一. 简介 二. 详细介绍 1. Adaptive Performance(自适应性能) 2.音频 3. 编辑器 4. 图形 5. 输入管理器 6.包管理器 7.物理 8.2D物理 9.玩家 10.预设管理器 一. 简介 在 Unity 中&#xff0c;Project Settings&#xff08;项目设置&#xff09;页面是一个非常…

企业组网中MPLS和SD-WAN方案各有什么特点?

MPLS&#xff08;多协议标签交换&#xff09;和SD-WAN&#xff08;软件定义广域网&#xff09;是企业组网的两大关键技术方案&#xff0c;各自具备独特的特点和优势。 MPLS作为一种传统的专线技术&#xff0c;通过给数据包附加标签&#xff0c;实现了网络流量的高效转发。这种方…

教你如何安装并使用小熊猫c++

目录 前言 一、获取安装包 二、安装 1.打开安装包 2.选择语言 3.接受协议 4.安装场景 5.选择组件 6.选择位置 7.完成安装 三、如何使用 1.打开软件并选择主题 2.创建项目 3.新建空项目 4.创建源文件 5.测试C语言代码 6.编译 7.运行 总结 前言 已有的C/…

地震采集的观测系统

这张图表明&#xff0c;为什么在速度分析论文中&#xff0c;与CMP有关的数据都有CDP序号的影子——因为CDP序号是对一条测线上布置的观测系统对地下反射点的信号记录。换句话说&#xff0c;不同的CDP序号&#xff08;类似测井位置&#xff09;意味着不同的CMP道集。 几种论文中…

【Python快速入门和实践013】Python常用脚本-目标检测之按照类别数量划分数据集

一、功能介绍 这段代码实现了从给定的图像和标签文件夹中分割数据集为训练集、验证集和测试集的功能。以下是代码功能的总结&#xff1a; 创建目标文件夹结构&#xff1a; 在指定的根目录&#xff08;dataset_root&#xff09;下创建images和labels两个文件夹。在这两个文件夹下…

scoket通信 -- 网络字节序

include <arpa/inet.h> 考虑到不同语言不同库函数的参数可能不同&#xff0c;我这里以c语言的arpa/inet.h库中的函数为例. 网络字节序是什么 网络字节序&#xff08;Network Byte Order&#xff09;是指在网络通信中用于数据交换时所采用的字节序&#xff0c;它是大端…

log4j日志配置%X{TransId}

log4j日志配置文件中的%X{TransId}是怎么动态获取值的 在Log4j中&#xff0c;%X{TransId} 是用来从MDC&#xff08;Mapped Diagnostic Context&#xff09;中获取值的占位符。MDC 是 Log4j 提供的一种机制&#xff0c;用于在同一个线程的不同日志记录中传递上下文信息。通过 M…

centos ssh免密登录配置

ssh免密登录 centos 系统中&#xff0c;配置免密需要确保ssh配置文件免密登录权限打开了 sudo vim /etc/ssh/sshd_config 查看PubkeyAuthentication值为yes 修改之后&#xff0c;重启sshd sudo systemctl restart sshd免密配置 &#xff08;1&#xff09;生成秘钥文件 ssh-…

Linux 下 RocketMQ 安装、配置与运维(详细讲解)

一 RocketMQ 下载安装 1 下载 RocketMQ&#xff1a; 下载当前最新版本RocketMQ 官网下载&#xff1a; https://dist.apache.org/repos/dist/release/rocketmq/5.3.0/rocketmq-all-5.3.0-bin-release.zip wget https://dist.apache.org/repos/dist/release/rocket…

安装搭建MongoDB及配置副本集

目录 一、什么是MongoDB的副本集 简介 &#xff08;1&#xff09;冗余和数据可用性 &#xff08;2&#xff09;MongoDB中的复制 &#xff08;3&#xff09;主从复制和副本集区别 二、副本集的架构 三、副本集的成员 四、部署副本集 1、节点划分 2、安装MongoDB 2.1、…

数据结构与算法——平衡二叉树

1、基本介绍 1&#xff09;平衡二叉树又叫平衡二叉搜索树(Self-balanceing binary search tree)&#xff0c;又被称为AVL树&#xff0c;可以保证查询效率较高。 2&#xff09;具有以下特点&#xff1a;它是一颗空树或它的左右两颗子树的高度差绝对值不超过1&#xff0c;并且左…

网络热门编程项目导学:尚医通

本文作者&#xff1a;程序员鱼皮 免费编程学习 - 编程导航网&#xff1a;https://www.code-nav.cn 现在网上有很多播放量巨高的免费编程项目教程&#xff0c;很多学编程的同学可能都看过&#xff0c;就导致大家可能写在简历上的内容都差不多。 于是就有了下面这张图&#xff1…

python之matplotlib (3 坐标轴设置)

写在前面 在说明坐标轴设置之前&#xff0c;我有必要和大家说清楚图像设置的一些方法&#xff0c;避免陷入困扰模糊的地步。前面我们说过&#xff0c;画图的三种方法&#xff08;python之matplotlib &#xff08;1 介绍及基本用法&#xff09;-CSDN博客&#xff09;。而设置也…

2024年证券从业资格考试题型特点及答题技巧

考试题型、题量、分值 证券从业科目题型题量&#xff1a; 一、单选题(每题0.5分&#xff0c;共40题&#xff0c;共20分) 下列每小题的四个选项中&#xff0c;只有一项是最符合题意的正确答案&#xff0c;多选、错选或不选均不得分。 二、多选题(每题1分&#xff0c;共40题&…