SpringBoot的拦截器中通过流 ( request.getInputStream() ) 的方式读取body中传来的数据会导致controller接收不到值

目前项目一般都前后端分离,但是前端再访问后端服务器时,都会有接口校验,如果用户没有登录是无法访问该接口的,因此我们需要定义拦截器,而在拦截器中验证有没有访问该接口的权限;

1、我们需要配置拦截器

@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Autowired
    private RequestAuthInterceptor requestAuthInterceptor;

    /**
     * 这个方法是用来配置静态资源的,比如html,js,css,等等
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    /**
     * 这个方法用来注册拦截器,
     * 我们自己写好的拦截器需要通过这里添加注册才能生效
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

//        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login", "/register");
        InterceptorRegistration registration = registry.addInterceptor(requestAuthInterceptor);
        //将这个controller放行
        registration.excludePathPatterns("/servicer");
        //拦截全部
        registration.addPathPatterns("/**");

    }

    @Bean
    public FilterRegistrationBean filterRegist() {
        FilterRegistrationBean frBean = new FilterRegistrationBean();
        frBean.setFilter(new RequestAuthFilter());
        frBean.addUrlPatterns("/*");
        return frBean;
    }

}

2、自定义拦截器

@Component
public class LoginInterceptor implements HandlerInterceptor {

    private static Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class);

    /**
     * 这个方法是在访问接口之前执行的,
     * 我们只需要在这里写验证登陆状态的业务逻辑,
     * 就可以在用户调用指定接口之前验证登陆状态了
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        LOG.info(" 请求地址 : " + request.getRequestURI());
        //每一个项目对于登陆的实现逻辑都有所区别,我这里使用最简单的Session提取User来验证登陆。
        HttpSession session = request.getSession();
        //这里的User是登陆时放入session的
//        String user = (String) session.getAttribute("user");
        //如果session中没有user,表示没登陆
//        if (user == null) {
        //这个方法返回false表示忽略当前请求,如果一个用户调用了需要登陆才能使用的接口,如果他没有登陆这里会直接忽略掉
        //当然你可以利用response给用户返回一些提示信息,告诉他没登陆
//            return false;
//        } else {
        //如果session里有user,表示该用户已经登陆,放行,用户即可继续调用自己需要的接口
        return true;
//        }
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) throws Exception {
    }
}

@Component
public class RequestAuthInterceptor implements HandlerInterceptor {

    private static Logger logger = LoggerFactory.getLogger(RequestAuthInterceptor.class);

    @Resource
    private ServicerMapper servicerMapper;

    /**
     * 统一使用开放平台的鉴权逻辑进行消息鉴权
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestUrl = request.getRequestURI();
        logger.info(" 请求地址 : " + requestUrl);


        response.setCharacterEncoding("utf-8");
        if(requestUrl.contains("error")){
            responseMessage(response, response.getWriter(), Result.error(CommonError.E_FF000000));
            return false;
        }
        // 1.首先检查消息头中参数是否完整 以及计算签名摘要

        String reqBody = new BodyReaderHttpServletRequestWrapper(request).getBodyString();

        //判断 openAccountId 是否存在
//        if (!partialCheck(request, response, reqBody)) {
//            Result commonResult = Result.error(CommonError.E_FF000001);
//            try {
//                responseMessage(response, response.getWriter(), commonResult);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//            return false;
//        }

        return true;
    }

    private boolean partialCheck(HttpServletRequest request, HttpServletResponse response, String bodyStr) {
        JSONObject paramsObject = JSONObject.parseObject(getParamRequestStr(request, bodyStr));
        logger.info("请求参数 :" + paramsObject);
        String openAccountId = paramsObject.getString("openAccountId");
        if (StringUtils.isBlank(openAccountId)) {
            return false;
        }
        //该url放行
        if (request.getRequestURI().contains("servicer/register")) {
            return true;
        }
        ServicerModel model = servicerMapper.getServicerByOpenAccountId(openAccountId);
        if (null == model) {
            return false;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) throws Exception {
    }

    //请求不通过,返回错误信息给客户端
    private static void responseMessage(HttpServletResponse response, PrintWriter out,
                                        Result commonResult) {
        response.setContentType("application/json; charset=utf-8");
        String json = JSON.toJSONString(commonResult);
        out.print(json);
        out.flush();
        out.close();
    }


    private static String getParamRequestStr(HttpServletRequest request, String bodyStr) {
        try {
            String result = bodyStr;
            //获取application/json类型的头请求内容的参数
            if (StringUtils.isBlank(bodyStr)) {
                Map<String, String> parmMap = new HashMap<String, String>();
                //方式二:
                Enumeration<String> a = request.getParameterNames();
                String parm = null;
                String val = "";
                while (a.hasMoreElements()) {
                    //参数名
                    parm = a.nextElement();
                    //值
                    val = request.getParameter(parm);
                    parmMap.put(parm, val);
                }
                return JSON.toJSONString(parmMap);
            }
            return result;
        } catch (Exception e) {
            logger.error("获取request参数错误", e);
        }
        return null;
    }

3、自定义HttpServletRequestWrapper

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        String sessionStream = getBodyString(request);
        body = sessionStream.getBytes(Charset.forName("UTF-8"));
    }

    public String getBodyString() {
        return new String(body, Charset.forName("UTF-8"));
    }

    /**
     * 获取请求Body
     *
     * @param request
     * @return
     */
    public String getBodyString(final ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = cloneInputStream(request.getInputStream());
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    /**
     * Description: 复制输入流</br>
     *
     * @param inputStream
     * @return</br>
     */
    public InputStream cloneInputStream(ServletInputStream inputStream) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        try {
            while ((len = inputStream.read(buffer)) > -1) {
                byteArrayOutputStream.write(buffer, 0, len);
            }
            byteArrayOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        return byteArrayInputStream;
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
}

原因:

这个问题其实就是一个流读取的问题,众所周知在Java中input流只能读取一次,主要原因是通标记的方法来判断流是否读取完毕(读取位 -1就是流读取完毕)解决这个问题我能想到两种方式

1、通过修改标记的方式 ( inputstream.markSupported() 方法可以判断这个流是否支持 mark 和 reset 方法。他们分别是标记 和 重新定位流。

再回到问题上来我们可以先使用第一种方法判断 requet 中的inputStream 是否支持标记和重新定位。因为这种方式实现起来比较简单。无需考虑太多。

   @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
         boolean b = request.getInputStream().markSupported();
         System.out.println(b);
    }

// 输出结果为 false

上述代码会返回一个 false 那么很明显,request 中的 input 流是不支持标记和重新定位的。

2、将流赋值给一个 byte[] 数组,或者其他变量保存起来。下载读取流时就调用这个数组就行。

我们再考虑第二种方法,我们需要一个变量保存这个流。并且还要保证再拦截器和controller中都要拿到这个变量。

通过上方这个图我们可以知道,在 Filter 和 Inteceptor 中间有一层Servlet。而Servlet就是提交request的地方。所以我们要重写HttpServletRequest方法只能在Servlet之前。也就是filter 中。下面就是直接上代码了 

@WebFilter(filterName = "requestAuthFilter", urlPatterns = "/*")
public class RequestAuthFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("--------------过滤器初始化------------");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {


        System.out.println("--------------执行过滤操作------------");

        // 防止流读取一次后就没有了, 所以需要将流继续写出去
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);

        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("--------------过滤器销毁------------");
    }
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Javarequest.getInputStream()方法可以用来读取HTTP请求的请求体内容。如果请求体是一个文件,可以通过该方法获取文件的二进制数据并进行处理。下面是一个示例代码片段,可以从HTTP请求读取文件内容并将其保存到本地磁盘上: ```java import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class FileUploadServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { InputStream inputStream = request.getInputStream(); String saveFilePath = "/path/to/save/uploaded/file"; OutputStream outputStream = new FileOutputStream(saveFilePath); int bytesRead = -1; byte[] buffer = new byte[4096]; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.close(); inputStream.close(); System.out.println("文件已成功上传!"); } } ``` 在这个示例,我们在Servlet实现了doPost()方法,用于处理POST请求。该方法调用request.getInputStream()方法来获取HTTP请求的输入,然后使用Java IO类将其写入本地文件。可以通过修改saveFilePath变量来指定保存文件的路径。最后,我们关闭输入和输出,并打印一条消息,指示文件已成功上传。 需要注意的是,在处理文件上传时,还需要在HTTP请求头设置Content-Type属性为multipart/form-data,以便服务器能够正确地解析请求体。否则,请求体内容可能被服务器解析为普通的文本数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值