JSP 过滤器

过滤器在 JSP 应用里的典型用法:编码、登录、缓存、日志。

Intermediate
Key Points过滤器对所有 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.

编码过滤器

Java
@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);
    }
}

登录校验过滤器

Java
@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.

禁用缓存的过滤器

Java
@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.

响应压缩

Java
@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);
    }
}
Tomcat's 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.

顺序问题

XML
<!-- 注解方式无法保证顺序,需要顺序时用 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.

Friends & Partners