JSP 过滤器
过滤器在 JSP 应用里的典型用法:编码、登录、缓存、日志。
为什么用过滤器
If every JSP starts with request.setCharacterEncoding("UTF-8"), one forgotten page garbles. A filter is written once and applies to all requests — impossible to forget.
编码过滤器
@WebFilter(urlPatterns = "/*")
public class CharsetFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
chain.doFilter(req, resp);
}
}登录校验过滤器
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {
private static final List<String> OPEN = List.of(
"/login.jsp", "/doLogin", "/css/", "/js/", "/favicon.ico");
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse w = (HttpServletResponse) resp;
String path = r.getRequestURI().substring(r.getContextPath().length());
boolean open = OPEN.stream().anyMatch(path::startsWith);
if (open) {
chain.doFilter(req, resp);
return;
}
Object uid = r.getSession().getAttribute("uid");
if (uid == null) {
w.sendRedirect(r.getContextPath() + "/login.jsp?from=" + path);
return;
}
chain.doFilter(req, resp);
}
}Match the whitelist by prefix with startsWith, not equals — otherwise /css/site.css gets blocked and the login page renders unstyled.
禁用缓存的过滤器
@WebFilter(urlPatterns = {"/admin/*", "/user/*"})
public class NoCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse w = (HttpServletResponse) resp;
w.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
w.setHeader("Pragma", "no-cache");
w.setDateHeader("Expires", 0);
chain.doFilter(req, resp);
}
}After logout, pressing Back can show the previous user's cached data — a classic privacy incident. Pages with personal data must disable caching.
响应压缩
@WebFilter(urlPatterns = "*.jsp")
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse w = (HttpServletResponse) resp;
String ae = r.getHeader("Accept-Encoding");
if (ae == null || !ae.contains("gzip")) {
chain.doFilter(req, resp);
return;
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
chain.doFilter(req, new Wrapper(w, buf));
byte[] raw = buf.toByteArray();
byte[] gz = gzip(raw);
w.setHeader("Content-Encoding", "gzip");
w.setContentLength(gz.length);
w.getOutputStream().write(gz);
}
}compression attribute and Nginx both do gzip; you rarely need a filter. Hand-rolling tends to miss Content-Length, HEAD requests and already-compressed images.顺序问题
<!-- 注解方式无法保证顺序,需要顺序时用 web.xml -->
<filter-mapping>
<filter-name>charsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>The charset filter must run before the login filter: the login filter may read parameters (such as from), and if encoding is not set yet, Chinese comes back garbled.
为什么用过滤器
如果每个 JSP 开头都写 request.setCharacterEncoding("UTF-8"),只要有一个页面忘了,那个页面就乱码。过滤器只写一次,对所有请求生效,忘不了。
编码过滤器
@WebFilter(urlPatterns = "/*")
public class CharsetFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
chain.doFilter(req, resp);
}
}登录校验过滤器
@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {
private static final List<String> OPEN = List.of(
"/login.jsp", "/doLogin", "/css/", "/js/", "/favicon.ico");
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse w = (HttpServletResponse) resp;
String path = r.getRequestURI().substring(r.getContextPath().length());
boolean open = OPEN.stream().anyMatch(path::startsWith);
if (open) {
chain.doFilter(req, resp);
return;
}
Object uid = r.getSession().getAttribute("uid");
if (uid == null) {
w.sendRedirect(r.getContextPath() + "/login.jsp?from=" + path);
return;
}
chain.doFilter(req, resp);
}
}白名单要用 startsWith 匹配前缀,而不是 equals——否则 /css/site.css 这种静态资源也会被拦,登录页会变成没有样式的纯文本。
禁用缓存的过滤器
@WebFilter(urlPatterns = {"/admin/*", "/user/*"})
public class NoCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse w = (HttpServletResponse) resp;
w.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
w.setHeader("Pragma", "no-cache");
w.setDateHeader("Expires", 0);
chain.doFilter(req, resp);
}
}用户登出后点浏览器「后退」,如果页面被缓存,仍能看到上一个人的数据——这是很典型的隐私事故。涉及个人数据的页面必须禁缓存。
响应压缩
@WebFilter(urlPatterns = "*.jsp")
public class GzipFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest r = (HttpServletRequest) req;
HttpServletResponse w = (HttpServletResponse) resp;
String ae = r.getHeader("Accept-Encoding");
if (ae == null || !ae.contains("gzip")) {
chain.doFilter(req, resp);
return;
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
chain.doFilter(req, new Wrapper(w, buf));
byte[] raw = buf.toByteArray();
byte[] gz = gzip(raw);
w.setHeader("Content-Encoding", "gzip");
w.setContentLength(gz.length);
w.getOutputStream().write(gz);
}
}compression 属性)和 Nginx 都能做 gzip,通常不需要自己在过滤器里写。自己实现容易漏掉 Content-Length、HEAD 请求、已压缩的图片等问题。顺序问题
<!-- 注解方式无法保证顺序,需要顺序时用 web.xml -->
<filter-mapping>
<filter-name>charsetFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>loginFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>编码过滤器必须排在登录过滤器之前:登录过滤器可能读参数(比如取 from),如果那时编码还没设,读到的中文就是乱码。