基于Java与Struts2框架的老旧Web应用CSRF攻击防护方案咨询
基于Java+Struts2老旧Web应用的CSRF防护方案
优先使用Struts2原生CSRF拦截器
如果你的Struts2版本在2.1.6及以上,直接用官方自带的CSRF拦截器是最省心的方案,不用自己造轮子:
- 在
struts.xml中修改拦截器栈,把csrf拦截器加入默认栈:
<interceptors> <interceptor-stack name="defaultStack"> <interceptor-ref name="csrf"/> <!-- 保留原有其他拦截器配置 --> </interceptor-stack> </interceptors> <!-- 全局应用该栈 --> <default-interceptor-ref name="defaultStack"/>
- 前端表单页面中,用Struts2标签生成CSRF token隐藏域:
<s:token/>,这个标签会自动在session中生成并存储token,提交表单时拦截器会自动校验请求中的token与session中的是否一致。 - 若涉及AJAX请求,需要手动把token放到请求头(比如
X-CSRF-Token)中,拦截器同样会识别并校验。
自定义Servlet过滤器的正确实现(解决过滤器瓶颈)
如果你的Struts2版本过低不支持原生拦截器,自定义过滤器时要注意几个关键要点,避免踩坑:
- 过滤器顺序必须在Struts2调度器之前
在web.xml中,你的CSRF过滤器要放在Struts2的StrutsPrepareAndExecuteFilter前面,确保请求先经过CSRF校验再进入Struts2处理:
<filter> <filter-name>CsrfFilter</filter-name> <filter-class>com.yourcompany.web.filter.CsrfFilter</filter-class> </filter> <filter-mapping> <filter-name>CsrfFilter</filter-name> <url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> <dispatcher>FORWARD</dispatcher> </filter-mapping> <!-- Struts2调度器放在后面 --> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
- 核心过滤器逻辑实现
过滤器只需要校验修改状态的请求(POST/PUT/DELETE等),GET类安全请求可直接放行,同时支持表单参数和请求头两种token传递方式:
public class CsrfFilter implements Filter { private static final String CSRF_TOKEN_SESSION_KEY = "CSRF_TOKEN"; private static final String CSRF_PARAM_NAME = "_csrf"; private static final String CSRF_HEADER_NAME = "X-CSRF-Token"; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; String method = req.getMethod().toUpperCase(); // 放行安全请求方法,首次访问时生成CSRF token并存入session if ("GET".equals(method) || "HEAD".equals(method) || "OPTIONS".equals(method)) { if (req.getSession().getAttribute(CSRF_TOKEN_SESSION_KEY) == null) { req.getSession().setAttribute(CSRF_TOKEN_SESSION_KEY, UUID.randomUUID().toString()); } chain.doFilter(request, response); return; } // 校验CSRF token String sessionToken = (String) req.getSession().getAttribute(CSRF_TOKEN_SESSION_KEY); String requestToken = req.getParameter(CSRF_PARAM_NAME); // 优先从请求头获取(适配AJAX) if (requestToken == null) { requestToken = req.getHeader(CSRF_HEADER_NAME); } if (sessionToken == null || !sessionToken.equals(requestToken)) { res.sendError(HttpServletResponse.SC_FORBIDDEN, "无效的CSRF令牌"); return; } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
- 前端配合实现
- JSP表单中添加隐藏域:
<input type="hidden" name="_csrf" value="${sessionScope.CSRF_TOKEN}"/> - AJAX请求时在请求头中携带token:
- JSP表单中添加隐藏域:
var csrfToken = "${sessionScope.CSRF_TOKEN}"; $.ajax({ url: "/your-action", type: "POST", headers: { "X-CSRF-Token": csrfToken }, // 其他请求参数 });
额外补充防护措施
- Referer头校验:在过滤器中增加Referer校验,确保请求来自可信域名(注意:部分浏览器可能不发送Referer,不能作为唯一防护手段)
- 敏感操作二次验证:对转账、修改密码等核心操作,额外添加验证码或密码确认步骤,提升安全性
- 尽量升级Struts2版本:老旧版本不仅可能缺少原生CSRF防护,还存在其他已知安全漏洞,升级到稳定版本能从根源减少风险
内容的提问来源于stack exchange,提问作者Varun Pattar
相关产品推荐
相关产品推荐

