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注入

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值