web篇过滤器拦截器监听器

Posted 小崔编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了web篇过滤器拦截器监听器相关的知识,希望对你有一定的参考价值。

项目中之前也都用过这些东西,现在写出来,一是记录在项目中的使用,二是全面认识一下三大利器。思来想去,我认为这三大利器总的目的是为了解耦,让代码更加简洁可读,所以问题来了,它是怎样实现的呢?这种方式是不是我也可以用到我自己编码的过程中呢?这都要自己一点点去探索,可能这篇不能马上解决这个问题,但在后续的文章会慢慢解读。

目录

一、过滤器和拦截器的区别

二、监听器

三、使用场景

四、总结    


一、过滤器和拦截器的区别

  • 使用范围不一样。过滤器是作用在servlet容器中的,而且拦截器是spring组件,作用在spring中的。
  • 使用的资源不一样。由于第一点,过滤器只能涉及到request/repnose等;而拦截器归spring管理,spring中的配置、bean等都可以使用。
  • 深度不一样。过滤器只在servlet前后起作用;而过滤器可以在方法前后、异常抛出前后等,拦截器有更大的灵活性,故在spring架构中,建议优先使用拦截器

二、监听器

监听器的作用是监听一些事件的发生从而进行一些操作,比如监听ServletContext,HttpSession的创建,销毁,从而执行一些初始化加载配置文件的操作,当Web容器启动后,Spring的监听器会启动监听,监听是否创建ServletContext的对象,如果发生了创建ServletContext对象这个事件(当web容器启动后一定会生成一个ServletContext对象,所以监听事件一定会发生),ContextLoaderListener类会实例化并且执行初始化方法,将spring的配置文件中配置的bean注册到Spring容器中

常用的监听器:

1.ServletContextListener -- 监听servletContext对象的创建以及销毁

    1.1    contextInitialized(ServletContextEvent arg0)   -- 创建时执行

    1.2    contextDestroyed(ServletContextEvent arg0)  -- 销毁时执行

2.HttpSessionListener  -- 监听session对象的创建以及销毁

    2.2   sessionCreated(HttpSessionEvent se)   -- 创建时执行

    2.2   sessionDestroyed(HttpSessionEvent se) -- 销毁时执行

3.ServletRequestListener -- 监听request对象的创建以及销毁

    3.1    requestInitialized(ServletRequestEvent sre) -- 创建时执行

    3.2    requestDestroyed(ServletRequestEvent sre) -- 销毁时执行

4.ServletContextAttributeListener  -- 监听servletContext对象中属性的改变

    4.1    attributeAdded(ServletContextAttributeEvent event) -- 添加属性时执行

    4.2    attributeReplaced(ServletContextAttributeEvent event) -- 修改属性时执行

    4.3    attributeRemoved(ServletContextAttributeEvent event) -- 删除属性时执行

5.HttpSessionAttributeListener  --监听session对象中属性的改变

    5.1    attributeAdded(HttpSessionBindingEvent event) -- 添加属性时执行

    5.2    attributeReplaced(HttpSessionBindingEvent event) -- 修改属性时执行

    5.3    attributeRemoved(HttpSessionBindingEvent event) -- 删除属性时执行

6.ServletRequestAttributeListener  --监听request对象中属性的改变

    6.1    attributeAdded(ServletRequestAttributeEvent srae) -- 添加属性时执行

    6.2    attributeReplaced(ServletRequestAttributeEvent srae) -- 修改属性时执行

    6.3    attributeRemoved(ServletRequestAttributeEvent srae) -- 删除属性时执行

7.自定义监听器

三、使用场景

拦截器:

参数校验(sql注入检验)、做xss攻击校验(两种方式,一种是流,一种是直接获取),做校验签名,做鉴权,请求计数(限流)等。

这里遇到的问题是做xss攻击时,两种方式

第一种是:有的controller方法是直接从request中获取的此参数 

public class XssFilter implements Filter   
  
    @Override  
    public void destroy()   
        // TODO Auto-generated method stub  
  
      
  
    @Override  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException   
        XSSRequestWrapper xssRequest = new XSSRequestWrapper ((HttpServletRequest) request);  
        chain.doFilter(xssRequest, response);  
      
  
    @Override  
    public void init(FilterConfig arg0) throws ServletException   
        // TODO Auto-generated method stub  
  
      
  
public class XSSRequestWrapper extends HttpServletRequestWrapper 

    /**
     * 构造方法
     *
     * @param request
     */
    public XSSRequestWrapper(HttpServletRequest request) 
        super(request);
    

    /**
     * 处理参数值
     */
    @Override
    public String[] getParameterValues(String parameter) 
        String[] values = super.getParameterValues(parameter);
        if (values == null) 
            return null;
        
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) 
            encodedValues[i] = dealString(values[i]);
        
        return encodedValues;
    

    /**
     * 覆盖
     *
     * @param value
     * @return
     * @date 上午9:30:43
     * @author DuChaoWei
     * @descripte
     */
    @Override
    public String getParameter(String parameter) 
        String value = super.getParameter(parameter);
        return dealString(value);
    

    @Override
    public String getHeader(String name) 
        String value = super.getHeader(name);
        return dealString(value);
    

    /**
     * 字符串处理
     *
     * @param value
     * @return
     * @date 上午9:30:43
     * @author DuChaoWei
     * @descripte
     */
    private String dealString(String value) 
        if (value != null) 
            // 采用spring的StringEscapeUtils工具类 实现
            StringEscapeUtils.escapehtml(value);
            StringEscapeUtils.escapejavascript(value);
            StringEscapeUtils.escapeSql(value);
        
        return value;
    

第二种是:有的controller方法是通过注解@ReuqestBody转换成实体对象获取的参数


public class CheckSQLInjectionFilter implements Filter 
	
	private List<String> excludes = new ArrayList<>();
	
	public void setExcludes(List<String> excludes) 
		this.excludes = excludes;
	
	public List<String> getExcludes() 
		return excludes;
	
 
 
	@Override
	public void init(FilterConfig filterConfig) throws ServletException 
		String excludes = filterConfig.getInitParameter("excludes");
		if (StringUtil.isNotBlank(excludes)) 
			String[] array = excludes.split(",");
			for (String url : array) 
				this.excludes.add(url);
			
		
	
 
 
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException 
		HttpServletRequest req = (HttpServletRequest)request;
		HttpServletResponse resp = (HttpServletResponse)response;
		String requestPath = req.getRequestURI();
		requestPath = requestPath.substring(req.getContextPath().length() + 1);
		while (requestPath.endsWith("/")) //预防uri末尾有 ‘/’
			requestPath = requestPath.substring(0, requestPath.length()-1);
		
		for (String str : excludes) 
			if (str.endsWith("*")) 
				if (requestPath.startsWith(str.substring(0, str.length() - 1)))
					chain.doFilter(req, resp);
					return;
				
			
			if(str.equals(requestPath)) 
				chain.doFilter(req, resp);
				return;
			
		
		Map<String, Object> paramMap = new HashMap<>();
		String type = req.getContentType();
		ServletRequest requestWrapper = null;  
	    if(req instanceof HttpServletRequest)   
	    	   requestWrapper = new ReaderReuseHttpServletRequestWrapper(req); 
	    
		Reader reader = requestWrapper.getReader();
		// 读取Request Payload数据
		String Payload = IOUtils.toString(reader);
		if (type != null && type.startsWith("application/json"))
			JSONObject jsonObject = JSONObject.parseObject(Payload);
			if (jsonObject != null) 
				for(Map.Entry<String, Object> entry : jsonObject.entrySet()) 
					paramMap.put(entry.getKey(), entry.getValue());
				
			
		 else if(type != null && type.startsWith("text/plain")) 
			String[] kvs = Payload.split("&");
			for (String kv : kvs) 
				String[] lf = kv.split("=");
				paramMap.put(lf[0], lf[1]);
			
			
		
		// 获取请求参数
		Enumeration en = req.getParameterNames();
		while(en.hasMoreElements()) 
			String name = (String) en.nextElement();
			String value = req.getParameter(name);
			paramMap.put(name, value);
		
		for(Map.Entry<String, Object> node : paramMap.entrySet()) 
			boolean valid = true;
			if (node.getValue() instanceof String)
				valid = CheckSQLInjectionUtil.validate((String)node.getValue());
			if (!valid) 
				resp.setContentType("application/json;charset=UTF-8");
				PrintWriter writer = resp.getWriter();
				writer.write("\\"success\\":false,\\"msg\\":\\""+HttpStatus.SECURITY.getName()+"\\",\\"code\\":"+HttpStatus.SECURITY.getCode()+"");
				writer.flush();
				return;
			
		
		chain.doFilter(requestWrapper, resp);
	
 
 
	@Override
	public void destroy() 
		
	
	
	/**
	 * 两个方法都注明方法只能被调用一次,由于RequestBody是流的形式读取,
	 * 那么流读了一次就没有了,所以只能被调用一次。
	 * 既然是因为流只能读一次的原因,那么只要将流的内容保存下来,就可以实现反复读取了
	 * @author LIU
	 *
	 */
	public static class ReaderReuseHttpServletRequestWrapper extends HttpServletRequestWrapper 	
 
 
	    private final byte[] body;  
	      
	    public ReaderReuseHttpServletRequestWrapper(HttpServletRequest request)   
	    		throws IOException   
	        super(request);
	        body = IOUtils.toString(request.getReader()).getBytes(Charset.forName("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 int read() throws IOException 
	                return bais.read();
	            
	
	        ; 
	      
	

类似于上段代码,可以从流中获取参数,在一一校验。当然还有其他好多方法使用。

拦截器

从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。

比如:只有登录的用户才能有权限进入某些菜单。

监听器

https://www.jianshu.com/p/95f2ed98aaad

四、总结    

主要思想是尽可能的优化代码,使代码更加优雅,感觉这个很有趣,大家有好多的想法和思路,比如lombok是怎样简化代码的,深入其中,自得乐趣。

解决在Filter中读取Request中的流后, 然后在Controller中@RequestBody的参数无法注入而导致 400 错误_java_gchsh的博客-CSDN博客_requestbody sql注入

以上是关于web篇过滤器拦截器监听器的主要内容,如果未能解决你的问题,请参考以下文章

java web过滤器拦截器监听器的区别

SpringBoot学习笔记:过滤器监听器拦截器

SpringBoot 过滤器, 拦截器, 监听器 对比及使用场景

java过滤器监听器拦截器机制

过滤器拦截器监听器

过滤器拦截器 和 监听器 的区别!