CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种利用已登录用户身份在非本人意愿下发送请求的攻击方式。攻击者通过诱导用户访问恶意页面或点击恶意链接,借用浏览器自动携带 Cookie 的特性,伪造用户操作请求。Spring Boot 项目中通常集成 Spring Security 来获得开箱即用的 CSRF 防护能力,但实际配置中需要根据前后端架构灵活调整。本文将分别介绍 CSRF 攻击原理、Spring Security 默认防护机制、前后端分离场景下的自定义实现以及测试验证方法,帮助开发者构建安全的 Web 应用。

CSRF 攻击原理与危害
CSRF 攻击成立的前提是目标网站使用 Cookie 作为身份凭证,并且浏览器在发起跨站请求时会自动携带目标域下的 Cookie。举个例子,用户登录了网银系统,浏览器保存了包含会话标识的 Cookie。此时用户又访问了一个恶意网站,该网站中有一个自动提交的表单,表单的 action 指向网银的转账接口,参数为收款人账号和金额。由于恶意页面加载时会自动提交这个表单,而浏览器会携带目标域(网银)的 Cookie 发送请求,最终导致转账操作在用户不知情的情况下被执行。
CSRF 与 XSS 有本质区别。XSS 是攻击者把恶意脚本注入到目标网站页面中执行,从而窃取数据或篡改页面;而 CSRF 并不需要攻击者拿到 Cookie 内容,它只是诱导浏览器向目标网站发起请求,利用的是浏览器自动携带 Cookie 的机制。因此,即使 Cookie 设置了 HttpOnly 防止 JavaScript 读取,CSRF 攻击依然可以成功。常见的防护思路包括验证 Referer 或 Origin 头、使用验证码、引入一次性 Token 校验等。Spring Security 采用的是同步令牌模式,即服务端生成随机 Token,请求必须携带该 Token 才能通过校验。
从攻击流程可以看出,CSRF 的破坏力取决于目标接口的敏感程度。修改密码、转账、删除数据等操作都是高风险目标。对于只读接口,CSRF 攻击的危害相对有限,但实际项目中很难完全区分,通常建议对所有状态变更请求启用防护。
Spring Boot 集成 Spring Security 的默认 CSRF 配置
在 Spring Boot 中引入 spring-boot-starter-security 依赖后,CSRF 防护默认开启。对于使用 Thymeleaf 等服务端模板渲染的项目,表单提交会自动包含一个名为 _csrf 的隐藏字段。当请求到达后端,CsrfFilter 会从请求参数中读取 _csrf 的值并与 Session 中保存的 Token 进行比较,不一致或缺失则返回 403 拒绝请求。这种机制对传统 MVC 项目非常友好,几乎不需要额外代码就能获得基础防护。
但对于需要自定义安全策略的场景,可以通过 SecurityFilterChain 配置来调整 CSRF 行为。下面是一个使用 Spring Security 5.7 及以上版本的 Java 配置示例,将 Token 存储方式改为 Cookie,方便前后端分离架构使用:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.authorizeHttpRequests(auth -> auth
.antMatchers("/login", "/error").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form.defaultSuccessUrl("/home"));
return http.build();
}
}
代码中使用了 CookieCsrfTokenRepository.withHttpOnlyFalse(),它会把 CSRF Token 写入名为 XSRF-TOKEN 的 Cookie 中,并将 HttpOnly 属性设为 false,这样前端 JavaScript 才能读取该 Cookie。如果 HttpOnly 为 true,浏览器 API 无法访问 Cookie,前后端分离场景下就无法使用此方案。值得注意的是,对于纯 API 服务且没有浏览器跨站请求风险时,有些开发者会选择使用 http.csrf().disable() 关闭防护,但这会带来安全隐患,不推荐在涉及用户身份的操作中直接禁用。
在传统表单页面中,Thymeleaf 会自动为每个表单插入 Token 字段,不需要开发者手动处理。但如果使用 JSP 或其他模板,可能需要手动输出 Token。随着 Spring Security 版本的演进,WebSecurityConfigurerAdapter 已被废弃,推荐使用组件式配置,上面的示例即为新写法,适用于 Spring Boot 2.7 及 3.x 版本。
前后端分离场景下 CSRF 防护实现
前后端分离架构下,后端不再渲染 HTML,前端通过 Axios、Fetch 等工具发送 AJAX 请求,无法依赖模板引擎自动注入 Token。此时需要前后端配合,常用的方案是:后端使用 CookieCsrfTokenRepository 将 Token 放入非 HttpOnly 的 Cookie,前端在发送请求前读取该 Cookie 的值,并添加到请求头 X-XSRF-TOKEN 中。Spring Security 的 CsrfFilter 会优先从请求头中读取该值进行校验。
后端配置可以沿用上面的 SecurityConfig,但需要注意 CORS 配置。由于前端可能运行在另一个域名或端口下,需要允许携带凭据的跨域请求。下面是包含 CORS 支持的完整后端配置:
@Configuration
@EnableWebSecurity
public class ApiSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.authorizeHttpRequests(auth -> auth
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
}
前端需要统一处理请求头。以 Axios 为例,可以添加一个请求拦截器,从 Cookie 中读取 XSRF-TOKEN 并设置到请求头。代码如下:
import axios from 'axios';
axios.defaults.withCredentials = true;
axios.interceptors.request.use(config => {
const token = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
if (token) {
config.headers['X-XSRF-TOKEN'] = token;
}
return config;
});
上面的正则表达式用来解析 Cookie 字符串,提取 XSRF-TOKEN 的值。实际项目中也可以使用 js-cookie 等库简化读取过程。关键点是请求头名称必须为 X-XSRF-TOKEN,这是 Spring Security 默认支持的 Token 请求头,也可以通过自定义 CsrfTokenRepository 或 Filter 修改。当请求携带正确的 Token 后,CsrfFilter 会通过,否则返回 403。
还有一种做法是使用前后端约定的自定义请求头,比如 X-CSRF-TOKEN,但需要额外配置 Spring Security 的 CsrfFilter 参数。无论使用哪种请求头,核心思路都是确保请求中带上了与 Cookie 中 Token 匹配的值,并且攻击者无法从第三方域读取或设置该请求头,因为跨域请求默认无法携带自定义请求头,除非目标服务器通过 CORS 明确允许,而攻击者无法操纵目标服务器的 CORS 响应。这也解释了为什么 CSRF Token 方案在前后端分离下依然有效。
CSRF 防护测试与常见问题
验证 CSRF 防护是否生效,可以手动构造一个不带 Token 的 POST 请求,预期会返回 403 状态码。使用 curl 模拟时,先访问一次登录页面或者任意受保护页面,从响应头 Set-Cookie 中获取 XSRF-TOKEN,然后分别发送不带该 Token 和带正确 Token 的请求进行对比。如果带 Token 请求返回正常,不带 Token 请求被拒绝,说明防护配置成功。
在实际开发中,CSRF 防护常遇到一些诡异问题。例如前端已经配置了拦截器,但请求仍然被 403 拒绝,这通常是因为 CORS 预检请求(OPTIONS)没有正确通过。Spring Security 默认会拦截所有请求,OPTIONS 请求需要被 permitAll 放行,否则预检失败。此外,Cookie 作用域和域名配置不当也会导致 Token 无法传递,比如前端使用 localhost,后端使用 127.0.0.1,虽然指向同一台机器,但浏览器视为不同源,Cookie 不会共享。解决方法是统一使用域名或配置 Cookie 的 domain 属性。
另一个常见的误区是认为 SameSite Cookie 可以完全替代 CSRF Token。SameSite=Strict 或 Lax 确实能阻止大部分跨站请求携带 Cookie,从而缓解 CSRF 风险,但旧版浏览器不支持或支持不完整,而且某些场景下如从外部链接跳转,Lax 模式仍可能存在风险。因此,最佳实践是将 SameSite 属性与 CSRF Token 结合使用,形成纵深防御。Spring Security 的 CookieCsrfTokenRepository 默认不设置 SameSite,可以通过自定义 CookieSerializer 来添加该属性。
最后需要强调的是,CSRF 防护不能仅仅依赖后端配置,前端也应当对敏感操作增加确认步骤,例如输入验证码或二次密码。这样做即使 Token 被绕过,也能增加攻击成本。Spring Boot 与 Spring Security 的整合提供了灵活的 CSRF 防护能力,关键在于根据项目架构选择正确的实现方式,并充分测试跨域、Cookie 和 Token 的配合是否正常。
Spring BootCSRF防护Spring Security修改时间:2026-09-19 05:44:15