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 [email protected]
 *
 */
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 [email protected]
 * @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 [email protected]
 *
 */
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 [email protected]
 * @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 [email protected]
 * @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 [email protected]
     * @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 [email protected]
* @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万+

猜你喜欢

转载自blog.csdn.net/qierkang/article/details/88544691