首页 > 解决方案 > 带有正则表达式模式的 Tomcat 8.52 版本 CsrfPreventionFilter entryPoints 参数

问题描述

我正在使用 tomcat 8.52 来修复 CSRF 问题。在那使用 org.apache.catalina.filters.CsrfPreventionFilter。

  1. 如何将 entryPoints 参数与正则表达式模式匹配一​​起使用。

  2. 如何避免 CSRF 检查我的登录页面。

我的登录页面加载了 20 个 js、40 个图像、23 个 css。我可以在入口点参数中提及多少?

我的 web.xml:

    <filter>
    <filter-name>CsrfFilter</filter-name>
    <filter-class>org.apache.catalina.filters.CsrfPreventionFilter</filter-class>
   <init-param>
      <param-name>denyStatus</param-name>
      <param-value>404</param-value>
    </init-param>
  <init-param>
  <param-name>entryPoints</param-name>
    <param-value>/mUser/login,/js/encrypt.js,/js/json-min.js,/m    User/homepage,/dispatch/sendtemplate</param-value>
  </init-param>

当我尝试使用我的页面登录时,我只看到 encrypt.js,json-min.js 只加载了其他页面显示 404 错误。登录页面时还会出现 404 页面。

标签: csrf

解决方案


我像这样定义自己的 CsrfPreventionFilter 类,并将我的 JS 和 CSS 以及 img 放在一个名为“static”的文件夹中

package filters.myCatalina;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

public class MyCsrfPreventionFilter extends MyCsrfPreventionFilterBase {

    private Logger logger = Logger.getLogger(getClass().getName());

    private final Set<String> entryPoints = new HashSet<>();

    private int nonceCacheSize = 5;

    public void setEntryPoints(String entryPoints) {
        String values[] = entryPoints.split(",");
        for (String value : values) {
            this.entryPoints.add(value.trim());
        }
    }

    public void setNonceCacheSize(int nonceCacheSize) {
        this.nonceCacheSize = nonceCacheSize;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {

        ServletResponse wResponse = null;

        if (request instanceof HttpServletRequest &&
                response instanceof HttpServletResponse) {

            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse res = (HttpServletResponse) response;

            boolean skipNonceCheck = false;

            logger.info(getRequestedPath(req));

            if ("/".equals(getRequestedPath(req)))
                skipNonceCheck = true;

            if ("/static/".equals(getRequestedPath(req).substring(0, 8)))
                skipNonceCheck = true;

            if (MyConstants.METHOD_GET.equals(req.getMethod())
                    && entryPoints.contains(getRequestedPath(req))) {
                skipNonceCheck = true;
            }

            HttpSession session = req.getSession(false);

            @SuppressWarnings("unchecked")
            LruCache<String> nonceCache = (session == null) ? null
                    : (LruCache<String>) session.getAttribute(
                    MyConstants.CSRF_NONCE_SESSION_ATTR_NAME);

            if (!skipNonceCheck) {
                String previousNonce =
                        req.getParameter(MyConstants.CSRF_NONCE_REQUEST_PARAM);
                if (nonceCache == null || previousNonce == null ||
                        !nonceCache.contains(previousNonce)) {
                    res.sendError(getDenyStatus());
                    return;
                }
            }

            if (nonceCache == null) {
                nonceCache = new LruCache<>(nonceCacheSize);
                if (session == null) {
                    session = req.getSession(true);
                }
                session.setAttribute(
                        MyConstants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
            }

            String newNonce = generateNonce();

            nonceCache.add(newNonce);

            wResponse = new CsrfResponseWrapper(res, newNonce);
        } else {
            wResponse = response;
        }

        chain.doFilter(request, wResponse);
    }


    protected static class CsrfResponseWrapper
            extends HttpServletResponseWrapper {

        private final String nonce;

        public CsrfResponseWrapper(HttpServletResponse response, String nonce) {
            super(response);
            this.nonce = nonce;
        }

        @Override
        @Deprecated
        public String encodeRedirectUrl(String url) {
            return encodeRedirectURL(url);
        }

        @Override
        public String encodeRedirectURL(String url) {
            return addNonce(super.encodeRedirectURL(url));
        }

        @Override
        @Deprecated
        public String encodeUrl(String url) {
            return encodeURL(url);
        }

        @Override
        public String encodeURL(String url) {
            return addNonce(super.encodeURL(url));
        }

        /*
         * Return the specified URL with the nonce added to the query string.
         */
        private String addNonce(String url) {

            if (url == null) {
                return nonce;
            }

            String path = url;
            String query = "";
            String anchor = "";
            int pound = path.indexOf('#');
            if (pound >= 0) {
                anchor = path.substring(pound);
                path = path.substring(0, pound);
            }
            int question = path.indexOf('?');
            if (question >= 0) {
                query = path.substring(question);
                path = path.substring(0, question);
            }
            StringBuilder sb = new StringBuilder(path);
            if (query.length() > 0) {
                sb.append(query);
                sb.append('&');
            } else {
                sb.append('?');
            }
            sb.append(MyConstants.CSRF_NONCE_REQUEST_PARAM);
            sb.append('=');
            sb.append(nonce);
            sb.append(anchor);
            return sb.toString();
        }
    }

    protected static class LruCache<T> implements Serializable {

        private static final long serialVersionUID = 1L;

        // Although the internal implementation uses a Map, this cache
        // implementation is only concerned with the keys.
        private final Map<T, T> cache;

        public LruCache(final int cacheSize) {
            cache = new LinkedHashMap<T, T>() {
                private static final long serialVersionUID = 1L;

                @Override
                protected boolean removeEldestEntry(Map.Entry<T, T> eldest) {
                    if (size() > cacheSize) {
                        return true;
                    }
                    return false;
                }
            };
        }

        public void add(T key) {
            synchronized (cache) {
                cache.put(key, null);
            }
        }

        public boolean contains(T key) {
            synchronized (cache) {
                return cache.containsKey(key);
            }
        }
    }
}

我的 web.xml 配置

<filter>
        <filter-name>CSRF</filter-name>
        <filter-class>filters.myCatalina.MyCsrfPreventionFilter</filter-class>
        <init-param>
            <param-name>entryPoints</param-name>
            <param-value>/index.jsp,/,index.jsp</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CSRF</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

并在 jsp entryPoint

<INPUT type="hidden" name="CSRF_NONCE" value="<%=response.encodeUrl(null)%>">

这只是一个示例,但它可以工作,如果您使用的是 tomcat-catalina 依赖项,您应该在 org.apache.catalina.filters.Constants 类中使用“org.apache.catalina.filters.CSRF_NONCE”而不是“CSRF_NONCE”


推荐阅读