spring boot拦截器获取requestBody的巨坑

背景介绍

我们才采用springboot构件项目的时候,往往会使用拦截器来做一些事情,比如做权限验证,做请求日志审计等。就会获取http的请求body。但是http的请求body是inputStream的,获取了一次滞后就不允许再获取了。如果在拦截器(HandlerInterceptor)中获取了,Controller层就没法获取了。所以RequestWrapper的思想出现了,spring 的官方提供了org.springframework.web.util.ContentCachingRequestWrapper类。

但是我想说"有问题",可能是我姿势不对。先介绍网上和大模型给出的常用解决方案。

网上和大模型的解决方案

过滤器(WrappingFilter )

WrappingFilter 在Fliter的时候引入spring官网的Wrapper

java 复制代码
@Component
public class WrappingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
        filterChain.doFilter(requestWrapper, responseWrapper);
        if(!responseWrapper.isCommitted()){
            responseWrapper.copyBodyToResponse();
        }
    }
}

拦截器(MyInterceptor)

再自定义拦截器,MyInterceptor

java 复制代码
package com.shenyun.lyguide.config.interceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 拦截逻辑
        Date date=new Date();
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=simpleDateFormat.format(date);
        String uri=request.getRequestURI();

        // 获取requestBody
        String requestBody = "";
        if(request instanceof ContentCachingRequestWrapper ) {
            byte[]  bytes=((ContentCachingRequestWrapper)request).getContentAsByteArray();
            requestBody=new String(bytes, StandardCharsets.UTF_8);
        }

        //此次希望用requestBody,header,等验证签名,但是requestBody是null
        System.out.println("Interceptor Request Body: " + requestBody);
        System.out.printf("-----进入拦截器了%s,%s%n",time, uri);

        // 继续处理请求
        return true;
    }
}

controller

java 复制代码
    @PostMapping("/test")
    @ResponseBody
    public String test(HttpServletRequest request,
                       HttpServletResponse response,
                       @RequestBody MyRequestDTO myRequestDTO){
        String bodyJson=JSON.toJSONString(myRequestDTO);
        System.out.println("controller print:"+bodyJson);
        return "test";
    }

原理解释

结论

byte[] bytes=((ContentCachingRequestWrapper)request).getContentAsByteArray();

在拦截器MyInterceptor中ContentCachingRequestWrapper的`getContentAsByteArray()`无法获取到requestBody。

解释

ContentCachingRequestWrapper的getContentAsByteArray方法只有在getInputStream读取了数据之后才能使用而获取到数据。

进一步跟踪

如果要使用getInputStream读取数据,那我就随ta的先读取一次

这是错误的方法,我只是好说明原理

java 复制代码
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 拦截逻辑
        Date date=new Date();
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=simpleDateFormat.format(date);
        String uri=request.getRequestURI();

        // 获取requestBody
        String requestBody = "";
        if(request instanceof ContentCachingRequestWrapper ) {
            //故意从流中再读取数据
            ServletInputStream inputStream =request.getInputStream();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];  // 缓冲区大小为1024字节
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                byteArrayOutputStream.write(buffer, 0, bytesRead);  // 写入已读取的数据块
            }
            byte[] bytesTemp= byteArrayOutputStream.toByteArray();
            String body=new String(bytesTemp, StandardCharsets.UTF_8);
            System.out.println("Stream print:"+body);
            
            //因为上面读取了一次,所以这儿可以使用getContentAsByteArray()方法获取
            byte[]  bytes=((ContentCachingRequestWrapper)request).getContentAsByteArray();
            requestBody=new String(bytes, StandardCharsets.UTF_8);
        }

        //此次希望用requestBody,header,等验证签名,但是requestBody是null
        System.out.println("Interceptor Request Body: " + requestBody);
        System.out.printf("-----进入拦截器了%s,%s%n",time, uri);

        // 继续处理请求
        return true;
    }
}

但是

在拦截器中故意读取了一次流,之后也可以使用getContentAsByteArray读取数据了。但是Controller层就报错了,Controller层需要读取数据映射到RequestBody注解的DTO对象上,底层是用inputStream中读取数据的。而,因为在拦截器中已经读取了一次流了,所以request的inputStream就无法获取数据了,因为流读取了之后只能使用ContentCachingRequestWrapper的getContentAsByteArray方法读取数据。

所以,没有达到效果,这就是ContentCachingRequestWrapper的巨坑。

下一篇文章,介绍spring boot拦截器获取requestBody的真实实践。

相关推荐
葡萄城技术团队2 分钟前
GcExcel V9.0 新特性解密:VALUETOTEXT/ARRAYTOTEXT 双函数,让数据文本转换更精准高效
java
她说..2 分钟前
策略模式+工厂模式实现订单校验功能
java·spring boot·java-ee·简单工厂模式·策略模式
短剑重铸之日3 分钟前
《设计模式》第五篇:策略模式
java·后端·设计模式·策略模式
知行合一。。。5 分钟前
Linux--10---crontab -e定时任务
java·linux·运维
cyforkk14 分钟前
16、Java 基础硬核复习:网络编程的核心逻辑与面试考点
java·网络·面试
serve the people24 分钟前
python环境搭建 (五) Dockerfile 和 docker-compose.yml 核心作用
java·python·docker
独断万古他化31 分钟前
【Spring 事务】核心概念与实战:从手动控制到注解自动事务
java·spring·事务
马猴烧酒.33 分钟前
【团队空间|第十一天】基础功能实现,RBAC权限控制,ShardingSphere详解
java·开发语言·数据库
fengxin_rou33 分钟前
从 String 到 Zset:Redis 核心数据结构全解析及排行榜应用
java·开发语言·redis·多线程
世界尽头与你35 分钟前
CVE-2025-55752_ Apache Tomcat 安全漏洞
java·安全·网络安全·渗透测试·tomcat·apache