springboot 统一签名校验的实现过程

由于业务需要,所有post请求需要做签名校验,由于所有controller层统一处理,此处采用springmvc拦截器实现,经过尝试发现,request.getParameterMap()无法获取参数,原因是前端使用mutildata的方式传送的参数在body中,于是采用getInputStream()或getReader()实现,这样一来,controller无法使用@RequestBody获得参数,原因是,getInputStream()或getReader()只能被读取一次,经网上找资料,记录如下解决方案

环境:springboot

  1. 对HttpServletRequestWrapper进行包装,将请求体流读出来并存下来
public class BodyReaderRequestWrapper extends HttpServletRequestWrapper {

    private final byte[] body;

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

    /**
     * 获取请求Body
     */
    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: 复制输入流
     */
    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) {
            }
        };
    }
}
  1. 实现过滤器,所有request进行包装
@WebFilter(urlPatterns = "/*", filterName = "InterfaceFilter")
public class InterfaceFilter implements Filter {

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

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 防止流读取一次后就没有了, 所以需要将流继续写出去
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        // 这里将原始request传入,读出流并存储
        ServletRequest requestWrapper = new  BodyReaderRequestWrapper(httpServletRequest);
        // 这里将原始request替换为包装后的request,此后所有进入controller的request均为包装后的
        filterChain.doFilter(requestWrapper, servletResponse);// 
    }

    @Override
    public void destroy() {

    }
}
  1. 实现拦截器进行统一验签
@Configuration
public class SignCheckConfig extends WebMvcConfigurationSupport {

    @Resource
    private SysParaMemService sysParaMemService;
    @Value("${spring.profiles.active}")
    private String env = "";

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SignInterceptor())
                .addPathPatterns("/**");
        super.addInterceptors(registry);
    }

    /**
     * 签名验证器
     */
    private class SignInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (!(HttpMethod.POST.name().equals(request.getMethod()))) {
                return true;
            }

            if (server_env_dev.toLowerCase().equals(env)) {
                return true;
            }

            BodyReaderRequestWrapper servletRequestWrapper = new BodyReaderRequestWrapper(request);
            String params = servletRequestWrapper.getBodyString(request);
            if (StringUtils.isEmpty(params)) {
                request.getRequestDispatcher("/wap/user/checksign").forward(request, response);
            }
            JSONObject jsonObject = JSONObject.fromObject(params);

            String data = jsonObject.optString("data");
            String signc = jsonObject.optString("sign");

            String sign = SignUtils.compMd5Sign(data + sysParaMemService.getSignKey());
            if (!(sign.equals(signc))) {
                // 该服务会抛出验签失败异常
                request.getRequestDispatcher("/wap/user/checksign").forward(request, response);
            }
            return true;
        }
    }
}

你可能感兴趣的:(问题记录,框架学习)