6、Spring技术栈-过滤器使用

随着互联网的不断发展,web应用的互动性也越来越强。但正如一个硬币会有两面一样,在用户体验提升的同时安全风险也会跟着有所增加。此文我们就以如何防御XSS攻击为例给说说过滤器的使用。

我们有这么一个需求,用户访问我们的网站,点击留言链接进入留言页面。

这里写图片描述

在标题页面,如果用户输入了如下内容:

<script>alert('你是不是傻?')</script>

如果用户提交留言时系统不做任何处理,那么当用户进入留言列表或者留言详细页面的时候,系统就会自动弹出警告框。

这里写图片描述

这就是我们所熟知的跨站脚本攻击(XSS)的一个很简单的例子,那在我们系统研发过程中,如何防御类似于XSS这样的攻击呢?最好的办法就是用户提交数据时,对用户所提交的数据中含有的特殊字符进行转义。

那么如何在数据到达Controller之前对这些数据进行处理呢?我们此文就说说通过过滤器的方式来实现这个功能。

1、过滤器简介

Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。

它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。

2、过滤器功能

在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest 。

根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据。

在HttpServletResponse到达客户端之前,拦截HttpServletResponse 。

根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据。

3、过滤器接口

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;

    public void destroy();
}

Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,Web服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:

调用目标资源之前,让一段代码执行。

是否调用目标资源(即是否让用户访问web资源)。

web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。

在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。

web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。

4、接口解释

public void init(FilterConfig filterConfig) throws ServletException;//初始化

和编写Servlet程序一样,Filter的创建和销毁都是由Web服务器负责,web应用程序启动时,web服务器将创建Filter的实例对象,并调用其init方法,读取web.xml的配置,完成对象的初始化功能,从而为后续的用户请求做好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;//拦截请求

这个方法完成实际的过滤操作,当客户访问与过滤器相关联的url的时候,Servlet过滤器将先执行doFilter方法,FilterChain将用于访问后续的过滤器

public void destroy();//销毁

Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁,在Web容器卸载 Filter 对象之前被调用,该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。

FilterConfig接口

用户在配置filter时,可以使用为filter配置一些初始化参数,当web容器实例化Filter对象,调用其init方法时,会把封装了filter初始化参数的filterConfig对象传递进来。因此开发人员在编写filter时,通过filterConfig对象的方法,就可获得以下内容:

public String getFilterName();//获得Filter名称
public ServletContext getServletContext();//返回Servlet上下文对象的引用。
public String getInitParameter(String name);//返回在部署描述中指定名称的初始化参数的值。如果不存在返回null. 
public Enumeration<String> getInitParameterNames();//返回过滤器的所有初始化参数的名字的枚举集合。 

5、过滤器实现

新建XSSFilter ,写入如下代码:

/**
 * @Comment XSS过滤器
 * @Author Ron
 * @Date 2017年9月14日 下午2:46:24
 * @return
 */
public class XSSFilter implements Filter {

    FilterConfig filterConfig = null;

    /**
     * @Comment 
     * @Author Ron
     * @Date 2017年9月14日 下午3:34:39
     * @return
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    /**
     * @Comment 
     * @Author Ron
     * @Date 2017年9月14日 下午3:34:55
     * @return
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
    }

    /**
     * @Comment
     * @Author Ron
     * @Date 2017年9月14日 下午3:35:24
     * @return
     */
    @Override
    public void destroy() {
        this.filterConfig=null;
    }
}

新建XssHttpServletRequestWrapper,写入如下代码:

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 定义一个表,指定以那些字符开头的参数不需要转义,实现一些特殊需求
     * 如:使用富文本编辑器时,提交过来的数据已经经过转义,所以就不用再转义
     */
    private static String[] DONTCLEAR_TABLE_PRIFIX={"layedit_"};

    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /**
     * @Comment 重写getParameterValues方法,获取参数值转义后返回
     * @Author Ron
     * @Date 2017年9月14日 下午4:46:10
     * @return
     */
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if(isEscapeHtml(parameter)){
            return values;
        }
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = cleanXSS(values[i]);
        }
        return encodedValues;
    }

    /**
     * @Comment 重写getParameter方法,获取参数值转义后返回
     * @Author Ron
     * @Date 2017年9月14日 下午4:45:29
     * @return
     */
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if(isEscapeHtml(parameter)){
            return value;
        }
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    /**
     * @Comment 获取请求Header的内容,转化之后返回
     * @Author Ron
     * @Date 2017年9月14日 下午4:39:30
     * @return
     */
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null)
            return null;
        return cleanXSS(value);
    }

    /**
     * @Comment 是否转义
     * @Author Ron
     * @Date 2017年9月14日 下午4:58:38
     * @return
     */
    private boolean isEscapeHtml(String parameter) {
        for(int i = 0 ; i < DONTCLEAR_TABLE_PRIFIX.length; i++){
            String prefix = DONTCLEAR_TABLE_PRIFIX[i];
            if(parameter.startsWith(prefix)){
                return true;
            }
        }
        return false;
    }

    /**
     * @Comment 标签转化
     * @Author Ron
     * @Date 2017年9月14日 下午4:32:32
     * @return
     */
    private String cleanXSS(String value) {
        value = value != null ? StringEscapeUtils.escapeHtml4(value.trim()) : null;
        return value;
    }
}

在web.xml文件中进行如下配置

<!-- 配置xss过滤器 start-->
    <filter>
        <filter-name>XssFilter</filter-name>
        <filter-class>ron.blog.blog_pc.filter.XSSFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>XssFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>
    <!-- 配置xss过滤器 end-->

这样我们的过滤器就可以对所提交的数据进行转义,但是需要注意的是我们在实现过滤器的时候定义了一个以某些字符开头的参数不需要转义,是因为在我们系统研发过程中,可能会使用到一些富文本编辑器,然而在数据提交时这些富文本编辑器已经对数据进行过转义,所以我们提交之后就没有必要对数据再次进行转义,因为这样可能会造成数据的错误,所以这里我们需要排除这些已经进行过转义的内容。

项目源码:https://github.com/Ron-Zheng/blog-system

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RonTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值