写在前面:
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配置通过通配符,拦截所有静态资源即可。
字符编码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"
/>
记录日志也是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地址等信息。
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 {
}
}
权限是任何系统必须有的一个核心模块。
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() {
}
}
也就是所谓的脱敏操作。
内容替换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() {
}
}
GZIP目前浏览器和web服务器都已经自动携带,只是通过Filter看下如何完成的。
Filter也常用图像处理。
用Filter先吧图像数据缓存起来,然后对图像数据进行水印处理后输出到客户端浏览器。
如果有必要再看案例
为了减少DB交互,部分业务场景可以使用缓存机制,基本慎用,除非确保不怎么变动的场景。
XSLT转换是XML文件的功能之一,是利用XSLT样式文件将XML文件转换为其他格式。
自定义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()方法。