原文鏈接:https://blog.csdn.net/weixin_43935907/article/details/93888343
背景概述
最近公司要求在之前的項目APP接口里面加入端口校驗功能,實現起來很簡單,就是通過添加攔截器的方式,在interceptor中讀取端口參數,校驗該端口在配置文件中是否有存在,存在返回已占用,請重新輸入,不存在就添加該服務。
接口中主要是json格式,其對應的是payload請求,不可以通過request的getParameter()方法直接獲取,需要通過getRead或者getInputStream方法獲取流,通過流讀取body,將body轉化成JsonObject來獲取。讀取之后就出現了下面的錯誤。
{ "timestamp": "2019-06-27T04:02:37.865+0000", "status": 400, "error": "Bad Request", "message": "Required request body is missing: public void com.infosec.netsafess.controller.UserController.save(com.infosec.netsafess.entity.User)", "trace": "org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public void com.infosec.netsafess.controller.UserController.save(com.infosec.netsafess.entity.User)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat com.infosec.netsafess.filter.SignValidateFilter.doFilter(SignValidateFilter.java:28)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n", "path": "/user/save" }
大概的意思是說request里面的流只能使用一次,在攔截器中使用完了就不能再次使用,網上的攻略很多,有的說需要自己重寫HttpServletRequestWrapper,有的說通過spring自帶的ContentCachingRequestWrapper就可以,我復制過來了,然而並沒有什么用,還是報那個錯。但是我看網上有幾個都說這樣可以,后來才發現他們是在 chain.doFilter(requestWrapper, response)之后再通過requestWrapper.getContentAsByteArray方法獲取數據,或者在filter中讀取了inputstream之后,在controller接口方法中使用requestWrapper.getContentAsByteArray方法再獲取數據,這樣就不能使用@RequestBody自動注入參數了,這顯然不是我想要達到的效果。
我在他們的基礎上做了一些修改,主要做的是讀取了inputStream之后,再將數據寫回去,具體實現如下:
第一步重寫HttpServletRequestWrapper
package com.infosec.netsafess.filter; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.apache.commons.io.IOUtils; public class ContentCachingRequestWrapper extends HttpServletRequestWrapper{ private byte[] body; private BufferedReader reader; private ServletInputStream inputStream; public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException{ super(request); loadBody(request); } private void loadBody(HttpServletRequest request) throws IOException{ body = IOUtils.toByteArray(request.getInputStream()); inputStream = new RequestCachingInputStream(body); } public byte[] getBody() { return body; } @Override public ServletInputStream getInputStream() throws IOException { if (inputStream != null) { return inputStream; } return super.getInputStream(); } @Override public BufferedReader getReader() throws IOException { if (reader == null) { reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding())); } return reader; } private static class RequestCachingInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public RequestCachingInputStream(byte[] bytes) { inputStream = new ByteArrayInputStream(bytes); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readlistener) { } } }
配置過濾器
package com.infosec.netsafess.filter; import lombok.extern.java.Log; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Component @Log @WebFilter(filterName = "test",urlPatterns = "/*") public class SignValidateFilter implements Filter{ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request); String body = IOUtils.toString(requestWrapper.getBody(),request.getCharacterEncoding()); log.info(body); chain.doFilter(requestWrapper, response); } @Override public void destroy() { } }
在攔截器中使用
package com.infosec.netsafess.interceptor; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.infosec.netsafess.annotation.LoginRequired; import com.infosec.netsafess.filter.ContentCachingRequestWrapper; import org.apache.commons.io.IOUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; public class AuthorityInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(!(handler instanceof HandlerMethod)){ return true; } //START: 方法注解級攔截器 HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //判斷接口是否需要登錄 LoginRequired methodAnnotation = method.getAnnotation(LoginRequired.class); //有@LoginRequired注解,需要認證 if(methodAnnotation != null){ System.out.println("========================"); ContentCachingRequestWrapper requestWapper = null; if(request instanceof HttpServletRequest){ requestWapper = (ContentCachingRequestWrapper) request; } String body = IOUtils.toString(requestWapper.getBody(),request.getCharacterEncoding()); System.out.println(body); JSONObject obj = JSON.parseObject(body); System.out.println(obj); } return true; } }
補充一點:攔截器是在過濾器的doFilter方法中執行的,根據debugger