当我们用spring 拦截器实现权限校验的时候,往往我们需要从request中获取一些数据进行校验,但是当我们在拦截器获取到数据,getinputStream,那么在后续的action即:controller中我们获取不到request,这是为什么呢?因为java.util.Map所包装的HttpServletRequest对象的参数是不可改变的。 这句话的意思就是我们通过request.getInputStream.那么request就没有啦。那么我们后续的操作也就拿不到request啦。这就影响我们后续的操作。那么既然我们知道问题所在,我们只需要把request拿出来,复制一份,保存,当我们拦截器完成工作以后,我们将我们保存的request输入进去,就可以进行后续的操作。如果我们想要塞进去,那么我们就要实现一个过滤器,拦截器,还有HttpServletRequestWrapper包装器。拦截器与过滤器的区别:拦截器是基于java的反射机制的,而过滤器是基于函数回调。拦截器不依赖与servlet容器,过滤器依赖与servlet容器。拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次。执行顺序:过滤前 – 拦截前 – Action处理 – 拦截后 – 过滤后。个人认为过滤是一个横向的过程,首先把客户端提交的内容进行过滤(例如未登录用户不能访问内部页面的处理);过滤通过后,拦截器将检查用户提交数据的验证,做一些前期的数据处理,接着把处理后的数据发给对应的Action;Action处理完成返回后,拦截器还可以做其他过程(还没想到要做啥),再向上返回到过滤器的后续操作。
那么我们开始实现吧:
- 实现自己的HttpServletRequestWrapper 类,将request保存到wrapper的body字段中。
private final static Logger log = LoggerFactory.getLogger(RequestToJson.class); private final byte[] body; public RequestToJson(HttpServletRequest request) throws IOException { super(request); String sessionStream = getBodyString(request); log.info("RequestToJson body:"+sessionStream); body = sessionStream.getBytes(Charset.forName("UTF-8")); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); ServletInputStream servletInputStream = new ServletInputStream() { public int read() throws IOException { return byteArrayInputStream.read(); } @Override public boolean isFinished() { // TODO Auto-generated method stub return false; } @Override public boolean isReady() { // TODO Auto-generated method stub return false; } @Override public void setReadListener(ReadListener listener) { // TODO Auto-generated method stub } }; return servletInputStream; } //返回自己的inputStream @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } /** * 复制input输入流 * * @param inputStream * @return * @throws IOException */ public InputStream cloneInputStream(ServletInputStream inputStream) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; try { while ((len = inputStream.read(buffer)) > -1) { byteArrayOutputStream.write(buffer, 0, len); } byteArrayOutputStream.flush(); } catch (IOException e) { log.info("clone servletInputStream failed", e); throw e; } InputStream byInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); return byInputStream; } //获取request中的getInputStream,返回String字段 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) { log.info("get request body code error:", e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return sb.toString(); }
- 实现Filter接口,拦截器:过滤器,是在java web中,你传入的request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者struts的 action进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet或者 struts的action前统一设置字符集,或者去除掉一些非法字符
-
@Component @WebFilter(filterName = "NowidUserFilter", urlPatterns = "/user/*") public class NowidUserFilter implements Filter { private final static Logger log = LoggerFactory.getLogger(NowidUserFilter.class); @Override public void init(FilterConfig filterConfig) throws ServletException { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("execute NowidUserFilter 。。。。"); HttpServletRequest httpServletRequest = (HttpServletRequest) request; //保存到自我实现的包装器,body是不可改变 ServletRequest requestWrapper = new RequestToJson(httpServletRequest); chain.doFilter(requestWrapper, response); } @Override public void destroy() { // TODO Auto-generated method stub } }
实现拦截器,然后调用包装类中的方法返回字符串,就可以自我操作啦。我的实现是json,用的是阿里巴巴的fastjson
@Component public class NowIdUserStatusInterceptor implements HandlerInterceptor { private static Logger logger = LoggerFactory.getLogger(NowIdUserStatusInterceptor.class); @Autowired private RedisTemplate<String, String> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("NowIdUserStatusInterceptor>>>>preHandle"); String ip = Tool.getRemoteIp(request); RequestToJson reqJson = new RequestToJson(request); JSONObject jsonObject = JSONObject.parseObject(reqJson.getBodyString(request)); String phone = jsonObject.getString("phone"); String userId = jsonObject.getString("userid"); String redisuserid = redisTemplate.opsForValue().get("user"+ip+userId); String redisphone = redisTemplate.opsForValue().get("user" + ip + phone); logger.info("login user is :"+redisuserid +" |"+redisphone); if (redisuserid == null && redisphone == null) { // 获取项目路径+登录路径 response.sendRedirect(request.getContextPath() + "/admin/login"); return false; } else { // logger.info("拦截器校验通过" +request); return true; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub HandlerInterceptor.super.afterCompletion(request, response, handler, ex); }
这样就可以顺利的执行啦,在一开始的时候我没有实现过滤器,所以还是获取不到,所以这三个都是必须实现的