Spring Boot HandlerInterceptor拦截器 :Required request body is missing OR Stream closed

由于 request中getReader()和getInputStream()只能调用一次

所以在Controller里面方法上@ResponseBody回再次调用一次getInputStream()报错2种错误:

第一:HttpMessageNotReadableException: Required request body is missing

第二:exception is java.io.IOException: Stream closed

### 拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.
* 由于 request中getReader()和getInputStream()只能调用一次 导致在Controller @ResponseBody的时候获取不到 null 或Stream closed
* 在项目中,可能会出现需要针对接口参数进行校验等问题 如:Token

1、添加RepeatedlyRequestWrapper 类并继承 HttpServletRequestWrapper 包装类

/*
 * Copyright (c) 2019-2019 1-meifen.com
 * 1-meifen.com PROPRIETARY/CONFIDENTIAL.
 * All rights reserved.
 * author qierkang xyqierkang@163.com
 *
 */
package com.ymeifen.filter;

import com.ymeifen.StringUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

/**
 * @Title RepeatedlyReadRequestWrapper
 * @ProjectName com.ymeifen.filter
 * @Author qierkang xyqierkang@163.com
 * @Date Created in 2019-03-14 00:20
 * @Description [ 拦截器中,request中getReader()和getInputStream()只能调用一次,构建可重复读取inputStream的request.
 * 由于 request中getReader()和getInputStream()只能调用一次 导致在Controller @ResponseBody的时候获取不到 null 或Stream closed
 * 在项目中,可能会出现需要针对接口参数进行校验等问题 如:Token
 *
 * ]
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public RepeatedlyRequestWrapper(HttpServletRequest request)
            throws IOException {
        super(request);
        body = readBytes(request.getReader(), "utf-8");
    }

    @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 boolean isFinished() {
                return false;
            }

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

            @Override
            public void setReadListener(ReadListener listener) {

            }

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

    /**
     * 通过BufferedReader和字符编码集转换成byte数组
     * @param br
     * @param encoding
     * @return
     * @throws IOException
     */
    private byte[] readBytes(BufferedReader br,String encoding) throws IOException{
        String str = null,retStr="";
        while ((str = br.readLine()) != null) {
            retStr += str;
        }
        if (StringUtils.isNotBlank(retStr)) {
            return retStr.getBytes(Charset.forName(encoding));
        }
        return null;
    }
}

2、添加RepeatedlyReadFilter 过滤器

/*
 * Copyright (c) 2019-2019 1-meifen.com
 * 1-meifen.com PROPRIETARY/CONFIDENTIAL.
 * All rights reserved.
 * author qierkang xyqierkang@163.com
 *
 */
package com.ymeifen.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @Title RepeatedlyReadFilter
 * @ProjectName com.ymeifen.filter
 * @Author qierkang xyqierkang@163.com
 * @Date Created in 2019-03-14 00:21
 * @Description [ 一句话描述是什么作用 ]
 */
public class RepeatedlyReadFilter implements Filter {

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

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//        logger.debug("复制request.getInputStream流");
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request);
        }
        if (null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy() {

    }
}

3、接着是拦截器部分 创建LogHandlerInterceptor类,(这边针对了全局进行Token验证):

package com.ymeifen.filter;

import com.alibaba.fastjson.JSONObject;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.ymeifen.DateUtils;
import com.ymeifen.properties.ManageConfig;
import com.ymeifen.response.BaseResponse;
import com.ymeifen.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import springfox.documentation.spring.web.json.Json;

import javax.annotation.PostConstruct;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;

/**
 * @author qierkang xyqierkang@163.com
 * @Title: LogHandlerInterceptor.java
 * @date 2018年6月12日 上午3:31:46
 * @Description: TODO[拦截器 ]
 */
@Component
public class LogHandlerInterceptor extends HandlerInterceptorAdapter {

    private static Logger logger = LoggerFactory.getLogger(LogHandlerInterceptor.class);
    /**
     * @Fields urls : TODO[ 设置白名单用户 ]
     */
    private static String[] url = {"/manage/user/login","/manage/user/loginout", "/error"};
    public List<String> urlList = Arrays.asList(url);

    @Autowired
    private RedisService redisService;
    @Autowired
    private ManageConfig manageConfig;

    @PostConstruct
    private void init() {
        try {
            logger.info("EK初始化运营系统拦截器:[{}]操作时间[{}]",manageConfig.getPermOpen()==0?"❌拦截器关闭❌":"?拦截器开启?", DateUtils.getDateTime());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param @param  req
     * @param @param  response
     * @param @return
     * @param @throws Exception    设定文件
     * @throws
     * @author qierkang xyqierkang@163.com
     * @date 2018年1月4日 下午7:44:52
     * @Description: TODO[ 无权限访问返回 ]
     */
    private boolean responseNoPerm(HttpServletRequest req, HttpServletResponse response) throws Exception {
        PrintWriter out = null;
        response.setContentType("application/json;charset=UTF-8");
        out = response.getWriter();
        out.print(JSONObject.toJSONString(BaseResponse.errorNoPerm()));
        out.flush();
        return false;
    }

    private boolean responseTokenIsNull(HttpServletRequest req, HttpServletResponse response) throws Exception {
        PrintWriter out = null;
        response.setContentType("application/json;charset=UTF-8");
        out = response.getWriter();
        out.print(JSONObject.toJSONString(BaseResponse.errorNoToken()));
        out.flush();
        return false;
    }


    /**
     * *
     * controller 执行之前调用
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        RepeatedlyRequestWrapper requestWrapper = (RepeatedlyRequestWrapper) request;
        Gson gson = new Gson();
        if (manageConfig.getPermOpen() == 0) {
            return true;
        } else if (manageConfig.getPermOpen() == 1) {
            String url = request.getRequestURI().substring(request.getRequestURI().indexOf("/")+1);
            if (urlList.contains(url)) {
                //判断白名单是否存在合法url
                return true;
            }
            List<String> list = gson.fromJson(redisService.get("permUrlList"), new TypeToken<List<String>>() {}.getType());
            if (list == null || list.size() <= 0) {
                //非法连接 没有任何权限
                return this.responseNoPerm(request, response);
            }
            if(request.getParameter("token")==null){
                //post json提交判断方法
                JSONObject json= JSONObject.parseObject(getBodyString(requestWrapper));
                System.out.println(json);
                if(null==redisService.get(json.getString("token"))){
                    //在判断白名单之后 在进行每次进行token判断是否失效
                    return this.responseTokenIsNull(request, response);
                }
            }else{
                // get / post提交判断方法
                if(null==redisService.get(request.getParameter("token"))){
                    //在判断白名单之后 在进行每次进行token判断是否失效
                    return this.responseTokenIsNull(request, response);
                }
            }

            if (list.contains(url)) {
                return true;
            } else {
                return this.responseNoPerm(request, response);
            }
        }
        return this.responseNoPerm(request, response);
    }

    /**
     * controller 执行之后,且页面渲染之前调用
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
//		System.out.println("------postHandle执行之后,且页面渲染之前调用-----");
    }

    /**
     * 页面渲染之后调用,一般用于资源清理操作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
//		System.out.println("------afterCompletion 页面渲染之后调用,一般用于资源清理操作-----");

    }

    /**
     * 获取请求Body
     *
     * @param request
     *
     * @return
     */
    public static 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 static 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;
    }

}

4、接着Boot web 请求 拦截SpringBootWebConfig (WebMvcConfigurerAdapter 在Spring5.0已被废弃

package com.ymeifen.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import java.nio.charset.Charset;
import java.util.List;

/**
* @Title: SpringBootWebConfig.java
* @author qierkang xyqierkang@163.com
* @date 2019年03月14日01:14:47
* @Description: TODO[ 初始化拦截器 ]
*/
@Configuration
public class SpringBootWebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private LogHandlerInterceptor logHandlerInterceptor;
	/* (非 Javadoc)
	* <p>Title: addInterceptors</p>
	* <p>Description: </p>
	* @param registry
	* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addInterceptors(org.springframework.web.servlet.config.annotation.InterceptorRegistry)
	*初始化拦截器
	*/
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(logHandlerInterceptor).addPathPatterns("/**");;
	}

	@Bean
    public HttpMessageConverter<String> responseBodyConverter() {
        StringHttpMessageConverter converter = new StringHttpMessageConverter(
                Charset.forName("UTF-8"));
        return converter;
    }

    @Override
    public void configureMessageConverters(
            List<HttpMessageConverter<?>> converters) {
        super.configureMessageConverters(converters);

    }

    @Override
    public void configureContentNegotiation(
            ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false);
    }

}

最后测试:

LogHandlerInterceptor

//在这里使用
//RepeatedlyRequestWrapper requestWrapper = (RepeatedlyRequestWrapper) request;
//获取多次也不会影响到 因为InputStream 流被复制 Controller @ResponseBody 也不会获取不到
     @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        RepeatedlyRequestWrapper requestWrapper = (RepeatedlyRequestWrapper) request;
        Gson gson = new Gson();
        if (manageConfig.getPermOpen() == 0) {
            return true;
        } else if (manageConfig.getPermOpen() == 1) {
            String url = request.getRequestURI().substring(request.getRequestURI().indexOf("/")+1);
            if (urlList.contains(url)) {
                //判断白名单是否存在合法url
                return true;
            }
            List<String> list = gson.fromJson(redisService.get("permUrlList"), new TypeToken<List<String>>() {}.getType());
            if (list == null || list.size() <= 0) {
                //非法连接 没有任何权限
                return this.responseNoPerm(request, response);
            }
            if(request.getParameter("token")==null){
                //post json提交判断方法
                JSONObject json= JSONObject.parseObject(getBodyString(requestWrapper));
                System.out.println(json);
                if(null==redisService.get(json.getString("token"))){
                    //在判断白名单之后 在进行每次进行token判断是否失效
                    return this.responseTokenIsNull(request, response);
                }
            }else{
                // get / post提交判断方法
                if(null==redisService.get(request.getParameter("token"))){
                    //在判断白名单之后 在进行每次进行token判断是否失效
                    return this.responseTokenIsNull(request, response);
                }
            }

            if (list.contains(url)) {
                return true;
            } else {
                return this.responseNoPerm(request, response);
            }
        }
        return this.responseNoPerm(request, response);
    }

 

发布了33 篇原创文章 · 获赞 85 · 访问量 21万+
展开阅读全文

@RequestBody 实体类接收不到数据,但是用com.alibaba.fastjson.JSONObject可以

09-09

``` @RequestMapping(value = "/getTest", method = {RequestMethod.POST}) public @ResponseBody CommonResponse getTest(@RequestBody TestForm form { ``` 后台这样写,使用postman测试 请求会直接报400, ``` <!doctype html> <html lang="en"> <head> <title>HTTP Status 400 – Bad Request</title> <style type="text/css"> h1 { font-family: Tahoma, Arial, sans-serif; color: white; background-color: #525D76; font-size: 22px; } h2 { font-family: Tahoma, Arial, sans-serif; color: white; background-color: #525D76; font-size: 16px; } h3 { font-family: Tahoma, Arial, sans-serif; color: white; background-color: #525D76; font-size: 14px; } body { font-family: Tahoma, Arial, sans-serif; color: black; background-color: white; } b { font-family: Tahoma, Arial, sans-serif; color: white; background-color: #525D76; } p { font-family: Tahoma, Arial, sans-serif; background: white; color: black; font-size: 12px; } a { color: black; } a.name { color: black; } .line { height: 1px; background-color: #525D76; border: none; } </style> </head> <body> <h1>HTTP Status 400 – Bad Request</h1> <hr class="line" /> <p><b>Type</b> Status Report</p> <p><b>Description</b> The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). </p> <hr class="line" /> <h3>Apache Tomcat/8.5.42</h3> </body> </html> ``` 如果后台改为 ``` public CommonResponse getTest(@RequestBody JSONObject jsonObject) { ``` 用JSONObject接收则没有问题,请各位大佬告知原因 问答

springmvc框架,HandlerInterceptor拦截器跳转不了

12-15

**需求**:用户session十分钟内无请求就会自动删除。无论用户发送什么请求都要被拦截器拦截,并跳转到登录页面。 **问题**:拦截器能拦截到用户的访问行为,可是,跳转语句五法执行。 **我的代码**: HandlerInteceptor: ``` @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception { HttpSession session = request.getSession(true); // session中获取用户名信息 Object obj = session.getAttribute(Constant.LOGIN_SESSION); System.out.println("session中的数据:---> " + obj); if (obj == null || "".equals(obj.toString())) { String location = "/login.jsp" ; response.sendRedirect(location); return false; } return true; } ``` springmvc.xml拦截器配置: ``` <!-- Session失效拦截 --> <mvc:interceptors> <!-- 定义拦截器 --> <mvc:interceptor> <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> <mvc:mapping path="/**" /> <!-- 不需要拦截的地址 --> <!-- 直接用ip:端口号可以打开登录页 --> <mvc:exclude-mapping path="/" /> <!-- 可以浏览器地址栏输入login.jsp后缀访问登录页 --> <mvc:exclude-mapping path="/login.jsp" /> <!-- 以下为静态资源,不需要过滤 --> <mvc:exclude-mapping path="/App/**" /> <mvc:exclude-mapping path="/common/**" /> <mvc:exclude-mapping path="/css/**" /> <mvc:exclude-mapping path="/image/**" /> <mvc:exclude-mapping path="/js/**" /> <mvc:exclude-mapping path="/jsp/**" /> <mvc:exclude-mapping path="/kindeditor/**" /> <mvc:exclude-mapping path="/lib/**" /> <bean class="com.ryzh0310.interceptor.SystemSessionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> ``` web.xml中session删除配置(为了测试我写了最小时间1): ``` <!-- 登录退出session控制 --> <session-config> <session-timeout>1</session-timeout> </session-config> ``` 我的项目结构: ![图片说明](https://img-ask.csdn.net/upload/201712/15/1513299471_920782.png) 先谢谢你们啊 你们说我的重定向路径应该写成request.getContextPath() 而不应该直接写成/login.jsp 我打印了日志了,日志内容是: request.getContextPath() = /login.js 请帮我看下是不是其他问题吧。 我用的是springmvc+mybatis 抱歉,我是这么测试的,结果就是上面那行日志内容 ``` System.out.println("request.getContextPath() = " + request.getContextPath() + "/login.jsp"); ``` 先感谢大家了。 我改了改springmvc.xml配置文件, ``` <!-- Session失效拦截 --> <mvc:interceptors> <!-- 定义拦截器 --> <mvc:interceptor> <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> <mvc:mapping path="/**" /> <!-- 不需要拦截的地址 --> <mvc:exclude-mapping path="/login.jsp" /> <mvc:exclude-mapping path="/user/login" /> <mvc:exclude-mapping path="/App" /> <mvc:exclude-mapping path="/common" /> <mvc:exclude-mapping path="/css" /> <mvc:exclude-mapping path="/image" /> <mvc:exclude-mapping path="/js" /> <mvc:exclude-mapping path="/jsp" /> <mvc:exclude-mapping path="/kindeditor" /> <mvc:exclude-mapping path="/lib" /> <bean class="com.ryzh0310.interceptor.SystemSessionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors> ``` mvn:mapping Path由 "/" 改为 "/**" mvc:exclude-mapping path也由 "/file/**"改成了"/file" **现在的情况是:** 1.可以跳转到登录界面了,然而地址栏上显示的是被拦截的请求所请求的目标界面的url。 2.这样跳转来的登陆界面,根本不能登录,可以输入帐号密码,却不能发送登录请求 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览