Filter高级开发(二)——实现敏感字符过滤功能

来源:互联网 发布:毒药法术升级数据 编辑:程序博客网 时间:2024/05/21 18:50

在实际开发中,我们如果真正要做一个上线的网站,就要考虑到过滤敏感字符(敏感词)。
敏感词可分为三大类:

  • 禁用词,我们用数字1来表示。
  • 审核词,我们用数字2来表示。
  • 替换词,我们用数字3来表示。

为了实现敏感字符过滤功能,首先在网上下载一个敏感词库,然后在Eclipse中新建一个动态Web项目——day19_words。
这里我们介绍一个新技术:在项目day19_words下新建一个文件夹——config,如下:
这里写图片描述
然后,右键文件夹config→Build Path→Use as Source Folder(源目录),如下所示:
这里写图片描述
此时文件夹config就会在类目录路径下,最终会发布到classes目录下。
接着在config目录下创建一个cn.itcast.words包,将敏感词库拷贝到此包下,如下:
这里写图片描述
至此,前期的准备工作我们已经做好了。下面我们正式写代码来实现这一功能。
我们先在cn.itcast.web.filter包下编写一个敏感字符过滤器——WordsFilter.java。
这里写图片描述
现在我们思考这个过滤器该怎么写。当tomcat服务器启动day19_words这个web应用时,程序就要从config目录下的cn.itcast.words包下将敏感词库加载到内存中。也就是说该web应用一启动时,首先要将敏感词库读到系统里面去,而且敏感词库只要读一遍就行了。我们可以在静态代码块里面做,但是我们还有一种方法,可以在init方法里面做,因为filter对象只会创建一次,init方法也只会执行一次。
此时WordsFilter过滤器可以写为:

public class WordsFilter implements Filter {    private List<String> banWords = new ArrayList<String>(); // 禁用词    private List<String> auditWords = new ArrayList<String>(); // 审核词    private List<String> replaceWords = new ArrayList<String>(); // 替换词    // WordsFilter过滤器只要一装载,WordsFilter对象只要一创建出来,init方法就会执行,init方法一执行,就会往List集合里面装东西。    /*     * 首先要将敏感词库读到系统里面去,而且敏感词库只要读一遍就行了。     * 我们可以在静态代码块里面做,但是我们还有一种方法,可以在init方法里面做,     * 因为filter对象只会创建一次,init方法也只会执行一次。     */    @Override    public void init(FilterConfig filterConfig) throws ServletException {        try {            String path = WordsFilter.class.getClassLoader().getResource("cn/itcast/words").getPath();            File[] files = new File(path).listFiles();             for (File file : files) {                if (!file.getName().endsWith(".txt")) {                    continue;                }                BufferedReader br = new BufferedReader(new FileReader(file));                String line = null;                while ((line=br.readLine()) != null) {                    String[] s = line.split("\\|");                    if (s.length != 2) {                        continue;                    }                    if (s[1].trim().equals("1")) {                        banWords.add(s[0].trim());                    }                    if (s[1].trim().equals("2")) {                        auditWords.add(s[0].trim());                    }                    if (s[1].trim().equals("3")) {                        replaceWords.add(s[0].trim());                    }                }            }            System.out.println("hahaha"); // 作断点调试用        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)            throws IOException, ServletException {    }    @Override    public void destroy() {    }}

WordsFilter过滤器只要一装载,WordsFilter对象只要一创建出来,init方法就会执行,init方法一执行,就会往List集合里面装东西。此时我们可以在代码System.out.println("hahaha");处打一个断点进行调试,然后Watch三个List集合,会发现这三个List集合都装载了东西。
首先我们来检查提交数据中是否包含禁用词。
此刻我们是不知道客户机要提交过来的数据的,也即不知道客户机会以什么名称提交数据过来, 所以我们应得到客户机提交的所有数据,然后挨个检查,那怎么得到客户机提交的所有数据呢?——可得到客户机提交的所有数据的名称,然后挨个取出来检查即可。
此时WordsFilter过滤器的代码可以写为:

public class WordsFilter implements Filter {    private List<String> banWords = new ArrayList<String>(); // 禁用词    private List<String> auditWords = new ArrayList<String>(); // 审核词    private List<String> replaceWords = new ArrayList<String>(); // 替换词    // WordsFilter过滤器只要一装载,WordsFilter对象只要一创建出来,init方法就会执行,init方法一执行,就会往List集合里面装东西。    /*     * 首先要将敏感词库读到系统里面去,而且敏感词库只要读一遍就行了。     * 我们可以在静态代码块里面做,但是我们还有一种方法,可以在init方法里面做,     * 因为filter对象只会创建一次,init方法也只会执行一次。     */    @Override    public void init(FilterConfig filterConfig) throws ServletException {        try {            String path = WordsFilter.class.getClassLoader().getResource("cn/itcast/words").getPath();            File[] files = new File(path).listFiles();             for (File file : files) {                if (!file.getName().endsWith(".txt")) {                    continue;                }                BufferedReader br = new BufferedReader(new FileReader(file));                String line = null;                while ((line=br.readLine()) != null) {                    String[] s = line.split("\\|");                    if (s.length != 2) {                        continue;                    }                    if (s[1].trim().equals("1")) {                        banWords.add(s[0].trim());                    }                    if (s[1].trim().equals("2")) {                        auditWords.add(s[0].trim());                    }                    if (s[1].trim().equals("3")) {                        replaceWords.add(s[0].trim());                    }                }            }            System.out.println("hahaha"); // 作断点调试用        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) resp;        // 检查提交数据是否包含禁用词        /*         * 此刻是不知道客户机要提交过来的数据的,也即不知道客户机会以什么名称提交数据过来,         * 所以应得到客户机提交的所有数据,然后挨个检查,怎么得到客户机提交的所有数据呢?         * 可得到客户机提交的所有数据的名称,然后挨个取出来检查即可。         */        Enumeration<String> e = request.getParameterNames();        while (e.hasMoreElements()) {            String name = (String) e.nextElement();            String data = request.getParameter(name); // 乱码:82342949842934*76%,所以这里还要解决乱码            // String regexData = data.replaceAll(" +", "");            for (String regex : banWords) {                Pattern pattern = Pattern.compile(regex); // 编译regex这个正则表达式,得到代表此正则表达式的对象                Matcher m = pattern.matcher(data); // 看data数据里面有没有和该正则表达式相匹配的内容                if (m.find()) { // 匹配器的find方法若返回true,则客户机提交的数据里面有和正则表达式相匹配的内容                    request.setAttribute("message", "文章中包含非法词汇,请检查后再提交!!!");                    request.getRequestDispatcher("/message.jsp").forward(request, response);                    return;                }            }        }        chain.doFilter(request, response);    }    @Override    public void destroy() {    }}

编写玩上述WordsFilter过滤器之后,就要在web.xml文件中配置该过滤器了,但是一定得记住在配置该过滤器之前,一定得要配置解决全站中文乱码的过滤器,关于如何编写该过滤器可以参考我的笔记Filter高级开发(一)——使用Decorator模式包装request对象解决get和post请求方式下的中文乱码问题。

<filter>    <filter-name>CharacterEncodingFilter2</filter-name>    <filter-class>cn.itcast.web.filter.CharacterEncodingFilter2</filter-class></filter><filter-mapping>    <filter-name>CharacterEncodingFilter2</filter-name>    <url-pattern>/*</url-pattern></filter-mapping><filter>    <filter-name>WordsFilter</filter-name>    <filter-class>cn.itcast.web.filter.WordsFilter</filter-class></filter><filter-mapping>    <filter-name>WordsFilter</filter-name>    <url-pattern>/*</url-pattern></filter-mapping>

接着在WebRoot根目录下新建一个测试页面——form.jsp,form.jsp页面的内容如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>    <form action="/day19_words/ServletDemo1" method="post">        <textarea rows="5" cols="50" name="resume"></textarea>        <br /> <input type="submit" value="提交">    </form></body></html>

再在WebRoot根目录下新建一个全局消息显示页面——message.jsp,message.jsp页面的内容如下:

<%@ page language="java" contentType="text/html; charset=UTF-8"    pageEncoding="UTF-8"%><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Insert title here</title></head><body>    ${message }</body></html>

当有人在form.jsp页面中填写内容并提交时,交给一个Servlet处理,所以应在cn.itcast.web.servlet包下创建一个Servlet——ServletDemo1.java。
这里写图片描述
ServletDemo1类的具体代码如下:

public class ServletDemo1 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String data = request.getParameter("resume");        response.getWriter().write(data);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

现在我们来测试提交数据中是否包含禁用词,测试结果为:
这里写图片描述
虽然以上简单的测试通过了,但是人是非常复杂的动物,我们看一下在disu.txt文件中的禁用词。
这里写图片描述
有些人不会按照规矩来输入生儿子没屁眼这样的字符串,他更有可能输入生 儿 子 没 屁 眼这样的字符串,那么WordsFilter过滤器是检查不出来提交的数据中是否包含禁用词的,为了能够检查出来这样的禁用词,我们可以将那些破坏者提交的数据中的空格全部替换为"",然后用替换掉的数据去和生儿子没屁眼这样的正则表达式相匹配,并且我们另外还要保存一份破坏者提交的原始数据。所以WordsFilter过滤器中的doFilter(…)方法要修改为:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) resp;        // 检查提交数据是否包含禁用词        /*         * 此刻是不知道客户机要提交过来的数据的,也即不知道客户机会以什么名称提交数据过来,         * 所以应得到客户机提交的所有数据,然后挨个检查,怎么得到客户机提交的所有数据呢?         * 可得到客户机提交的所有数据的名称,然后挨个取出来检查即可。         */        Enumeration<String> e = request.getParameterNames();        while (e.hasMoreElements()) {            String name = (String) e.nextElement();            String data = request.getParameter(name); // 乱码:82342949842934*76%,所以还要解决乱码            String regexData = data.replaceAll(" +", "");            for (String regex : banWords) {                Pattern pattern = Pattern.compile(regex); // 编译regex这个正则表达式,得到代表此正则表达式的对象                Matcher m = pattern.matcher(regexData); // 看regexData数据里面有没有和该正则表达式相匹配的内容                if (m.find()) { // 匹配器的find方法若返回true,则客户机提交的数据里面有和正则表达式相匹配的内容                    request.setAttribute("message", "文章中包含非法词汇,请检查后再提交!!!");                    request.getRequestDispatcher("/message.jsp").forward(request, response);                    return;                }            }        }        chain.doFilter(request, response);    }

由于破坏者是人,他就会想方设法地进行破坏,他不会简单地输入生 儿 子 没 屁 眼这样的字符串,他更加有可能会输入生*儿*子*没$屁#眼等等等这样的字符串,总之,破坏者就一定会搞破坏,我们写代码去防他,实在是防不胜防!!!虽然我们的处境很恶劣,但是我们程序员还是要竭尽全力地去防住破坏者,这是我们的职责,即使防不住!我们可以修改disu.txt文件的内容,在一定程度上去防他们。
这里写图片描述
以上这只是在一定的程度上去防破坏者,并不是防守得水泄不通,如果非得防守得天衣无缝,那就对提交的数据进行审核,管理员审核通过才发表,只要管理员觉得有一点不符合正确政治思想的东西,就不给其发表。
我们在上面检查完提交数据中是否包含禁用词之后,接下来就要检查提交数据中是否包含审核词。
现在我们来思考一个问题:文章中包含审核词,一提交,那么系统会怎么处理呢?——过滤器检查,检查文章包含审核词,过滤器应该把审核词高亮,然后再存到数据库里面去,方便管理员去审核,你要是不高亮,一篇文章几千字,管理员从中找一个审核词,眼睛都要找瞎啊!
当有人在form.jsp页面中填写内容并提交时,要交给一个Servlet——ServletDemo1去处理。ServletDemo1的具体代码为:

public class ServletDemo1 extends HttpServlet {    protected void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        String data = request.getParameter("resume");        response.getWriter().write(data);    }    protected void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}

对于代码String data = request.getParameter("resume");而言,获取到的是原始数据,而不是高亮之后的数据,所以可以说request的getParameter方法是不行的,所以我们使用Decorator模式包装request对象,增强其getParameter方法。
那么此时WordsFilter过滤器的代码应该修改为:

public class WordsFilter implements Filter {    private List<String> banWords = new ArrayList<String>(); // 禁用词    private List<String> auditWords = new ArrayList<String>(); // 审核词    private List<String> replaceWords = new ArrayList<String>(); // 替换词    // WordsFilter过滤器只要一装载,WordsFilter对象只要一创建出来,init方法就会执行,init方法一执行,就会往List集合里面装东西。    /*     * 首先要将敏感词库读到系统里面去,而且敏感词库只要读一遍就行了。     * 我们可以在静态代码块里面做,但是我们还有一种方法,可以在init方法里面做,     * 因为filter对象只会创建一次,init方法也只会执行一次。     */    @Override    public void init(FilterConfig filterConfig) throws ServletException {        try {            String path = WordsFilter.class.getClassLoader().getResource("cn/itcast/words").getPath();            File[] files = new File(path).listFiles();             for (File file : files) {                if (!file.getName().endsWith(".txt")) {                    continue;                }                BufferedReader br = new BufferedReader(new FileReader(file));                String line = null;                while ((line=br.readLine()) != null) {                    String[] s = line.split("\\|");                    if (s.length != 2) {                        continue;                    }                    if (s[1].trim().equals("1")) {                        banWords.add(s[0].trim());                    }                    if (s[1].trim().equals("2")) {                        auditWords.add(s[0].trim());                    }                    if (s[1].trim().equals("3")) {                        replaceWords.add(s[0].trim());                    }                }            }            System.out.println("hahaha"); // 作断点调试用        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) resp;        // 检查提交数据是否包含禁用词        /*         * 此刻是不知道客户机要提交过来的数据的,也即不知道客户机会以什么名称提交数据过来,         * 所以应得到客户机提交的所有数据,然后挨个检查,怎么得到客户机提交的所有数据呢?         * 可得到客户机提交的所有数据的名称,然后挨个取出来检查即可。         */        Enumeration<String> e = request.getParameterNames();        while (e.hasMoreElements()) {            String name = (String) e.nextElement();            String data = request.getParameter(name); // 乱码:82342949842934*76%,所以还要解决乱码            // String regexData = data.replaceAll(" +", "");            for (String regex : banWords) {                Pattern pattern = Pattern.compile(regex); // 编译regex这个正则表达式,得到代表此正则表达式的对象                Matcher m = pattern.matcher(data); // 看data数据里面有没有和该正则表达式相匹配的内容                if (m.find()) { // 匹配器的find方法若返回true,则客户机提交的数据里面有和正则表达式相匹配的内容                    request.setAttribute("message", "文章中包含非法词汇,请检查后再提交!!!");                    request.getRequestDispatcher("/message.jsp").forward(request, response);                    return;                }            }        }        // 检查审核词        chain.doFilter(new MyRequset(request), response);    }    class MyRequset extends HttpServletRequestWrapper {        private HttpServletRequest request;        public MyRequset(HttpServletRequest request) {            super(request);            this.request = request;        }        @Override        public String getParameter(String name) {            String data = this.request.getParameter(name);            if (data == null) {                return null;            }            for (String regex : auditWords) {                Pattern p = Pattern.compile(regex);                Matcher m = p.matcher(data);                if (m.find()) { // 我有一把仿真手枪,你要电鸡吗??                    String value = m.group(); // 找出客户机提交的数据中和正则表达式相匹配的数据                    data = data.replaceAll(regex, "<font color='red'>" + value + "</font>");                }            }            return data;        }    }    @Override    public void destroy() {    }}

现在我们来测试提交数据中是否包含审核词,测试结果为:
这里写图片描述
我们在上面检查完提交数据中是否包含禁用词和审核词之后,最后就要检查提交数据中是否包含替换词了。
同检查提交数据中是否包含审核词一样,我们希望request的getParameter方法内部检查已提交的内容里面有没有替换词,如果包含替换词,直接替换返回,所以还要增强这个方法。所以,最终我们的WordsFilter过滤器就全部写好了,其具体代码为:

public class WordsFilter implements Filter {    private List<String> banWords = new ArrayList<String>(); // 禁用词    private List<String> auditWords = new ArrayList<String>(); // 审核词    private List<String> replaceWords = new ArrayList<String>(); // 替换词    // WordsFilter过滤器只要一装载,WordsFilter对象只要一创建出来,init方法就会执行,init方法一执行,就会往List集合里面装东西。    /*     * 首先要将敏感词库读到系统里面去,而且敏感词库只要读一遍就行了。     * 我们可以在静态代码块里面做,但是我们还有一种方法,可以在init方法里面做,     * 因为filter对象只会创建一次,init方法也只会执行一次。     */    @Override    public void init(FilterConfig filterConfig) throws ServletException {        try {            String path = WordsFilter.class.getClassLoader().getResource("cn/itcast/words").getPath();            File[] files = new File(path).listFiles();             for (File file : files) {                if (!file.getName().endsWith(".txt")) {                    continue;                }                BufferedReader br = new BufferedReader(new FileReader(file));                String line = null;                while ((line=br.readLine()) != null) {                    String[] s = line.split("\\|");                    if (s.length != 2) {                        continue;                    }                    if (s[1].trim().equals("1")) {                        banWords.add(s[0].trim());                    }                    if (s[1].trim().equals("2")) {                        auditWords.add(s[0].trim());                    }                    if (s[1].trim().equals("3")) {                        replaceWords.add(s[0].trim());                    }                }            }            System.out.println("hahaha"); // 作断点调试用        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) resp;        // 检查提交数据是否包含禁用词        /*         * 此刻是不知道客户机要提交过来的数据的,也即不知道客户机会以什么名称提交数据过来,         * 所以应得到客户机提交的所有数据,然后挨个检查,怎么得到客户机提交的所有数据呢?         * 可得到客户机提交的所有数据的名称,然后挨个取出来检查即可。         */        Enumeration<String> e = request.getParameterNames();        while (e.hasMoreElements()) {            String name = (String) e.nextElement();            String data = request.getParameter(name); // 乱码:82342949842934*76%,所以还要解决乱码            // String regexData = data.replaceAll(" +", "");            for (String regex : banWords) {                Pattern pattern = Pattern.compile(regex); // 编译regex这个正则表达式,得到代表此正则表达式的对象                Matcher m = pattern.matcher(data); // 看data数据里面有没有和该正则表达式相匹配的内容                if (m.find()) { // 匹配器的find方法若返回true,则客户机提交的数据里面有和正则表达式相匹配的内容                    request.setAttribute("message", "文章中包含非法词汇,请检查后再提交!!!");                    request.getRequestDispatcher("/message.jsp").forward(request, response);                    return;                }            }        }        // 检查审核词        // 检查替换词        chain.doFilter(new MyRequset(request), response);    }    class MyRequset extends HttpServletRequestWrapper {        private HttpServletRequest request;        public MyRequset(HttpServletRequest request) {            super(request);            this.request = request;        }        @Override        public String getParameter(String name) {            String data = this.request.getParameter(name);            if (data == null) {                return null;            }            for (String regex : auditWords) {                Pattern p = Pattern.compile(regex);                Matcher m = p.matcher(data);                if (m.find()) { // 我有一把仿真手枪,你要电鸡吗??                    String value = m.group(); // 找出客户机提交的数据中和正则表达式相匹配的数据                    data = data.replaceAll(regex, "<font color='red'>" + value + "</font>");                }            }            for (String regex : replaceWords) {                Pattern p = Pattern.compile(regex);                Matcher m = p.matcher(data);                if (m.find()) { // 我有一把仿真手枪,你要电鸡和四大舰队吗??                    data = data.replaceAll(regex, "*******");                }            }            return data;        }    }    @Override    public void destroy() {    }}

现在我们来测试提交数据中是否包含替换词,测试结果为:
这里写图片描述
至此,实现敏感字符过滤功能的过滤器我们才算写完了。

0 0