在现代 Web 应用安全体系中,CSRF(Cross-Site Request Forgery,跨站请求伪造)始终位列 OWASP Top 10 威胁之一。Spring Security 作为 Java 生态中最主流的应用安全框架,内置了 CSRF 防护机制,但许多开发者对其开启方式、配置细节以及常见误用缺乏系统认知,导致防护形同虚设。本文将围绕 Spring Security 中 CSRF 防护的完整生命周期展开,从原理到实践,帮助你在项目中正确落地这一关键安全能力。
一、CSRF 攻击的本质与危害
CSRF 攻击的核心在于:攻击者诱导已登录目标站点的用户,在不知情的情况下向该站点发起恶意请求。由于浏览器会自动携带目标站点的 Cookie(包括会话凭证),服务器会将该请求视为用户本人的合法操作。
一个典型的攻击场景是:用户登录了网上银行后,访问了攻击者控制的页面,该页面中隐藏了一个自动提交的表单,向银行转账接口发起 POST 请求。若银行未部署 CSRF 防护,这笔转账就会以用户的名义成功执行。
与 XSS(跨站脚本攻击)不同,CSRF 不依赖注入恶意脚本,而是利用浏览器的凭证自动携带机制。因此,防御 CSRF 的关键在于:确保请求确实由用户主动发起,而非第三方站点伪造。
二、Spring Security 默认开启的 CSRF 防护
从 Spring Security 4.x 开始,CSRF 防护对非 GET、HEAD、TRACE、OPTIONS 的请求默认启用。这意味着,只要你的项目引入了 Spring Security 依赖且未手动关闭,CSRF 防护就已经在起作用。
其底层机制是 Synchronizer Token Pattern(同步令牌模式):
- 服务端为每个会话生成一个随机 CSRF Token,存储在 HttpSession 中。
- 该 Token 通过
_csrf参数或X-CSRF-TOKEN请求头传递给前端。 - 对于每个修改状态的请求(POST、PUT、DELETE、PATCH),Spring Security 的
CsrfFilter会校验请求中的 Token 是否与会话中的 Token 一致。 - 校验失败则返回 403 Forbidden。
在 Thymeleaf 模板中,只需在表单中加上 th:action,Spring Security 会自动注入隐藏的 CSRF Token 字段:
<form th:action="@{/transfer}" method="post">
<!-- Spring Security 自动插入 _csrf 隐藏域 -->
<input type="text" name="amount" />
<button type="submit">转账</button>
</form>
对于前后端分离的 REST API,通常将 Token 通过 Cookie 下发,前端读取后放入请求头:
// 从 Cookie 中读取 XSRF-TOKEN,放入请求头
const token = document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ amount: 1000 })
});
三、精细化配置 CSRF 防护
3.1 自定义 Token 仓库
默认情况下,CSRF Token 存储在 HttpSession 中。在分布式部署或需要无状态会话的场景下,可以改用 Cookie 存储:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// 允许前端 JavaScript 读取 Cookie 中的 Token
);
return http.build();
}
注意 withHttpOnlyFalse() 是必要的,否则前端无法通过 JavaScript 读取该 Cookie。
3.2 指定需要防护的请求路径
并非所有请求都需要 CSRF 防护。对于纯无状态的 API(如使用 JWT 认证的接口),CSRF 防护反而会造成困扰。可以按路径精细控制:
http.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/webhook/**", "/api/public/**")
);
但请务必谨慎:只有在你确信该接口不依赖 Cookie 会话认证时,才应忽略 CSRF 防护。
3.3 自定义 Token 生成策略
Spring Security 默认使用 HttpSessionCsrfTokenRepository 配合 LazyCsrfTokenRepository。如需自定义随机性来源,可实现 CsrfTokenRepository 接口,或调整 Token 的生成逻辑以适应特定的合规要求。
四、常见误用与安全陷阱
4.1 盲目全局关闭 CSRF
这是最危险的误用。开发者常因调试接口报 403 而直接添加:
http.csrf(AbstractHttpConfigurer::disable);
一旦关闭,所有基于 Cookie 认证的接口都暴露在 CSRF 攻击之下。正确的做法是定位具体是哪个请求未携带 Token,而非一刀切关闭。
4.2 对无状态 API 的错误认知
有人认为“我用了 JWT,就不需要 CSRF 防护”。这要分情况讨论:
- 如果 JWT 存储在 localStorage 中,并通过
Authorization请求头传递,那么 CSRF 攻击无法自动携带该头,确实不需要 CSRF 防护。 - 如果 JWT 存储在 Cookie 中,浏览器会自动携带,则仍然需要 CSRF 防护。
关键判断标准:认证凭证是否由浏览器自动附加到跨站请求中。
4.3 将 Token 暴露在 URL 中
某些实现将 CSRF Token 作为查询参数附加在 GET 请求的 URL 上。这会导致 Token 通过 Referer 头、浏览器历史、服务器日志等渠道泄露。CSRF Token 应仅通过 POST 请求体或自定义请求头传递。
4.4 忽略 Token 的会话绑定
如果 Token 生成后未与用户会话绑定,攻击者可以预先获取一个合法 Token 并嵌入恶意表单中。Spring Security 默认将 Token 与 HttpSession 绑定,但在自定义实现时容易忽略这一点。
4.5 与 XSS 的混淆
CSRF 防护无法抵御 XSS。一旦站点存在 XSS 漏洞,攻击者可以直接读取页面中的 CSRF Token,从而绕过防护。因此,CSRF 防护必须与 XSS 防御协同部署,包括输出编码、Content-Security-Policy 响应头等措施。
五、安全加固建议清单
结合 SQL 注入、XSS 等 Web 安全威胁的整体防护,建议在项目中落实以下措施:
- 保持 CSRF 默认开启,仅在充分论证后对特定无状态接口例外处理。
- 使用 SameSite Cookie 属性:设置
SameSite=Lax或Strict,作为 CSRF 防护的额外纵深。 - 配合 CSP 响应头,限制脚本来源,降低 XSS 风险。
- 对 SQL 注入使用参数化查询,避免因数据泄露间接削弱 CSRF Token 的保密性。
- 对所有状态修改操作强制 POST/PUT/DELETE,避免 GET 请求产生副作用。
- 定期审计 Spring Security 配置,确保没有遗留的
csrf().disable()调用。
结语
Spring Security 的 CSRF 防护是一把双刃剑:用对了,它以极低的成本为应用筑起一道关键防线;用错了,要么形同虚设,要么阻碍正常开发。理解其同步令牌模式的工作原理,根据认证方式(Cookie vs Token)做出正确判断,避免全局关闭和 Token 泄露等常见陷阱,才能真正发挥这一安全机制的价值。在 Web 安全威胁日益复杂的今天,CSRF 防护不应是事后补救,而应是从项目初始化就纳入考量的基础安全实践。
未经允许不得转载:任鹏个人博客 » Spring Security 中 CSRF 防护的开启、配置与误用


朋友圈点赞图在线生成源码