Filter经典案例

滕翔飞
2023-12-01

回到首页☞

写在前面:

  • 1、核心是真正的认知request和response。
  • 2、完全自由的自定义response
  • 3、Filter本身的功能和配置

Filter的特性使它可以处理特殊的工作,例如权限验证,日志记录,数据压缩,数据加密,格式转换,图像处理等。

1、防盗链Filter

这个生产就用到了,因为OS资源入侵,通过窜改文件下载路径,去下载其它文件。

这个实力就是静态资源图片的 应用,在引用的时候通过Filter校验request中url是不是本网站的。

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    String referer = request.getHeader("referer");
    if (referer == null || !referer.contains(request.getServerName())) {
      request.getRequestDispatcher("/error.gif").forward(request, response);
    } else {
      chain.doFilter(req, resp);
    }
  }

大体逻辑如上,我们可以在这里校验用户的下载路径等等操作,从而防止他们用相对路径切换到root核心文件资源。
web.xml配置通过通配符,拦截所有静态资源即可。

2、字符编码Filter

字符编码Filter是最常见的Filter之一,常用来解决Tomcat等服务器里request,response乱码的问题。

package com.wht.demo.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * 解决乱码问题
 *
 * @author JDIT
 */
@WebFilter(filterName = "CharacterEncodingFilter")
public class CharacterEncodingFilter implements Filter {
  private String characterEncoding;

  public void init(FilterConfig config) throws ServletException {
    characterEncoding = config.getInitParameter("characterEncoding");
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    if (null != characterEncoding) {
      req.setCharacterEncoding(characterEncoding);
      resp.setCharacterEncoding(characterEncoding);
    }
    chain.doFilter(req, resp);
  }

  public void destroy() {
  }

}

<filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>com.wht.demo.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>characterEncoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

页面编码方式与Filter编码方式必须一致,另外,如果表单是GET方式提交的,还需要修改Tomcat的Server.xml
指定编码格式,否则依然会乱码:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"
URIEncoding="UTF-8"
 />

3、日志记录Filter

记录日志也是Filter擅长的工作之一。在Request之前和之后都可以记录日志。该日志记录Filter使用apache的日志工具记录日志,记录客户的IP地址,访问的URL以及小号的时间。apache的commons-logging的好处是可以自由配置,弹性很大。

package com.wht.demo.filter;

import org.apache.log4j.Logger;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @author JDIT
 */
@WebFilter(filterName = "LogFilter")
public class LogFilter implements Filter {
  private final Logger logger = Logger.getLogger(this.getClass());
  public void destroy() {
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    logger.info("日志过滤器doFilter 发起请求。。。。。。。。。。。。。。。。。。。。");
    chain.doFilter(req, resp);
    logger.info("日志过滤器doFilter 响应请求。。。。。。。。。。。。。。。。。。。。");
  }

  public void init(FilterConfig config) throws ServletException {
    logger.info("日志过滤器初始化还。。。。。。。。。。。。。。。。。。。。");

  }

}

[INFO ] 2020-02-11 21:42:52,881 method:com.wht.demo.filter.LogFilter.doFilter(LogFilter.java:19)
日志过滤器doFilter 发起请求。。。。。。。。。。。。。。。。。。。。
[INFO ] 2020-02-11 21:42:52,882 method:com.wht.demo.servlet.HelloServlet.service(HelloServlet.java:40)
执行 service() 方法…
[INFO ] 2020-02-11 21:42:52,882 method:com.wht.demo.filter.LogFilter.doFilter(LogFilter.java:21)
日志过滤器doFilter 响应请求。。。。。。。。。。。。。。。。。。。。

通过日志我们可以看到这个调用链,在filter中我们可以记录很多信息,用户ip地址等信息。

4、异常捕获Filter

Filter中,如果在chain.doFilter上加上一个try…catch语句,就能捕获Servlet中抛出的可预料的与不可预料的异常,然后根据不同的异常进行不同的异常处理。

package com.wht.demo.filter;

import org.apache.log4j.Logger;

import javax.security.auth.login.AccountException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * @author JDIT
 */
@WebFilter(filterName = "ExceptionHandlerFilter")
public class ExceptionHandlerFilter implements Filter {
  private final Logger logger = Logger.getLogger(this.getClass());

  public void destroy() {
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {

    try{
      chain.doFilter(req, resp);
    }catch (Exception e){
      //根异常
      Throwable rootCause = e;
      while(rootCause.getCause() !=null){
        //循环直到找到跟异常为止
        rootCause =rootCause.getCause();
      }

      String msg = rootCause.getMessage();

      req.setAttribute("e",e);
      req.setAttribute("msg",msg);
      logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+rootCause.getClass().getName());
      if (rootCause instanceof AccountException){
        req.getRequestDispatcher("/accountException.jsp").forward(req,resp);
      }else{
        req.getRequestDispatcher("/exception.jsp").forward(req,resp);
      }
    }
  }

  public void init(FilterConfig config) throws ServletException {

  }

}

5、权限校验filter

权限是任何系统必须有的一个核心模块。
Java Web程序一般使用session或者cookie来记录用户是否登录,以及该用户的权限。权限验证Filter是将request提交给Servlet之前,对session或者cookie进行校验。如果没有响应的登录信息,或者权限不够,则进行相应的处理。

如果只是检查是佛度鞥路,则只检查session或cookie中有没有响应信息即可。如果要检查权限,则需要一个URI与权限角色的检查规则,检查规则一般存储在配置文件中,或者数据库中。其实一个企业系统架构往往是SOA架构,权限会有专门的平台确保安全,通过服务来校验权限。当然往往是系统启动时调用配置中心服务,把基础配置读入内存中。

package com.wht.demo.filter;

import javax.security.auth.login.AccountException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

/**
 * 权限校验Filter
 *
 * @author JDIT
 */
@WebFilter(filterName = "PriviledgeFilter")
public class PriviledgeFilter implements Filter {
  private Properties pp = new Properties();

  /**
   * 把配置文件加载进来作为基础数据比对
   *
   * @param config
   * @throws ServletException
   */
  public void init(FilterConfig config) throws ServletException {
    String file = config.getInitParameter("file");
    String realPath = config.getServletContext().getRealPath(file);
    try {
      pp.load(new FileInputStream(realPath));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    String requestURI = request.getRequestURI().replace(request.getContextPath() + "/", "");
    String action = req.getParameter("action");
    action = action == null ? "" : action;
    String uri = requestURI + "?action=" + action;
    String role = (String) request.getSession(true).getAttribute("role");
    role = role == null ? "gust" : role;
    boolean authentificated = false;
    for (Object obj : pp.keySet()) {
      String key = (String) obj;
      if (uri.matches(key.replace("?", "\\?").replace(".", "\\.").replace("*", ".*"))) {
        if (role.equals(pp.get(key))) {
          authentificated = true;
          break;
        }
      }
    }
    if (!authentificated) {
      throw new RuntimeException(new AccountException("你无权限访问该页面,请以可是的身份登录后查看。"))
    }
    chain.doFilter(req, resp);
  }

  public void destroy() {
  }

}

6、内容替换Filter

也就是所谓的脱敏操作。

内容替换Filter的工作原理是,在Servlet将内容输出到response时,response将内容缓存起来,在Filter中进行替换,然后再输出到客户端浏览器。由于默认的response并不能严格地缓存输出内容,因此需要自定义一个具备缓存功能的response。
可以通过扩展javax.servlet.http.HttpServletResponseWrapper类来实现自定义response。该类实现了javax.servlet.http.HttpServletResponse接口的所有方法,根据需要覆盖其中的方法即可。

package com.wht.demo.filter;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author JDIT
 */
public class HttpCharacterResponseWrapper extends HttpServletResponseWrapper {
  private CharArrayWriter charArrayWriter = new CharArrayWriter();

  public HttpCharacterResponseWrapper(HttpServletResponse response) {
    super(response);
  }

  @Override
  public PrintWriter getWriter() throws IOException {
    return new PrintWriter(charArrayWriter);
  }

  public CharArrayWriter getCharArrayWriter() {
    return charArrayWriter;
  }
}

该类覆盖了getWriter()方法,当servlet中使用该response对象调用getWriter来输出内容时,内容将被输出到CharArrayWriter对象中,达到缓存的效果。

注意:如果response输出的内容为字符类内容,则会调用getWriter方法;如果为二进制内容,如图像数据等,则会调用getOutputStream。本Filter只用于替换字符类内容,因此该自定义response只覆盖getWriter方法即可,而不用覆盖getOutputStream方法

package com.wht.demo.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;

/**
 * @author JDIT
 */
@WebFilter(filterName = "OutputReplaceFilter")
public class OutputReplaceFilter implements Filter {
  private Properties pp = new Properties();

  public void init(FilterConfig config) throws ServletException {
    String file = config.getInitParameter("file");
    String realPath = config.getServletContext().getRealPath(file);
    try {
      pp.load(new FileInputStream(realPath));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
    HttpCharacterResponseWrapper responseWrapper = new HttpCharacterResponseWrapper((HttpServletResponse) resp);
    chain.doFilter(req, resp);
    String output = responseWrapper.getCharArrayWriter().toString();
    for (Object obj : pp.keySet()) {
      String key = (String) obj;
      output = output.replace(key, pp.getProperty(key));
      PrintWriter out = resp.getWriter();
      out.write(output);
      out.println(">>>>>>>>>>>>>>>>>>内容以替换");
    }
  }

  public void destroy() {
  }

}

7、GZIP压缩Filter

GZIP目前浏览器和web服务器都已经自动携带,只是通过Filter看下如何完成的。

  • 1、判断浏览器是否支持
  • 2、文件压缩
    具体内容有空可以百度下案例。

8、图像水印Filter

Filter也常用图像处理。
用Filter先吧图像数据缓存起来,然后对图像数据进行水印处理后输出到客户端浏览器。

如果有必要再看案例

9、缓存Filter

为了减少DB交互,部分业务场景可以使用缓存机制,基本慎用,除非确保不怎么变动的场景。

  • 截取浏览器提交的request
  • 如果request 为POST方式,则不经过缓存
  • 如果request为GET方式,且请求的页面有缓存并且缓存的没有过期,则直接返回缓存结果,这样就避免了读取数据库。
  • 如果没有缓存或者缓存已过期,则重新请求Servlet,将Servlet返回的内容缓存并输出到客户浏览器。

10、XSLT转换Filter

XSLT转换是XML文件的功能之一,是利用XSLT样式文件将XML文件转换为其他格式。

11、文件上传Filter

自定义response可以改造响应,有时候还要改造request就要自定义request。
自定义request与自定义response类似,需要重载HttpServletRequestWrapper。

众所周知,上传文件的时候需要把<form>标签enctype设为“multipart/form-data”。而默认的request不能处理这种multipart的表单,执行request.getParameter(fieldname)只能得到null,更无法直接从request中获取文件。
自定义一个request,不仅能够通过rquest.getParameter(fieldname)获取普通的文本域,还能通过request.getAttribute(fieldname)直接获取长传的文件。原理是先判断原request是否为multipart格式,如果不是,则不做任何处理。如果是,则使用apache的uploading工具解析,放到Map中,并覆盖getParamer()方法与getAttribute()方法。

回到首页☞

 类似资料: