一、CSRF攻击原理与危害
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱骗已认证用户在不知情的情况下执行非预期的操作。
![图片[1]_浅析Spring Security如何防止CSRF攻击_知途无界](https://zhituwujie.com/wp-content/uploads/2025/06/d2b5ca33bd20250618105034.png)
典型攻击流程:
- 用户登录受信任网站A,本地保存了认证Cookie
- 用户未登出网站A的情况下访问恶意网站B
- 网站B的页面中包含伪造请求(如自动提交的表单)
- 浏览器自动携带网站A的Cookie发送请求
- 网站A服务器认为这是用户的合法操作
潜在危害:
- 用户数据篡改(修改个人信息)
- 资金转移(银行转账)
- 权限提升(修改账户权限)
- 内容发布(社交媒体发帖)
二、Spring Security的CSRF防护机制
Spring Security提供了开箱即用的CSRF防护方案,主要通过以下技术实现:
1. 同步令牌模式(Synchronizer Token Pattern)
核心原理:
- 服务器生成随机令牌(CSRF Token)
- 令牌存储在服务器会话中
- 每次提交表单必须携带该令牌
- 服务器验证令牌有效性
Spring Security实现细节:
public class CsrfFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) {
// 生成或获取现有令牌
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
if (csrfToken == null) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
// 验证请求(非安全方法需要验证)
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
// 实际验证逻辑
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
accessDeniedHandler.handle(request, response,
new AccessDeniedException("Invalid CSRF token"));
return;
}
filterChain.doFilter(request, response);
}
}
2. 默认启用与配置
Spring Security 4.x+ 默认启用CSRF保护,如需自定义配置:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/api/public/**")
.and()
// 其他配置...
}
}
关键配置项:
csrfTokenRepository:指定令牌存储策略HttpSessionCsrfTokenRepository(默认,会话存储)CookieCsrfTokenRepository(Cookie存储)ignoringAntMatchers:排除不需要防护的路径
3. 令牌生成与验证流程
令牌生命周期:
- 首次请求:生成令牌并存入Session/Cookie
- 页面渲染:将令牌值注入表单隐藏域或HTTP头
- 表单提交:携带令牌发送请求
- 服务器验证:比对提交令牌与存储令牌
- 验证通过:处理请求;失败:拒绝请求
三、前端集成方案
1. 传统表单集成
Spring Security自动支持Thymeleaf、JSP等模板引擎:
<form method="post">
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
<!-- 其他表单字段 -->
</form>
2. 单页应用(SPA)集成
Cookie方案(推荐):
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
前端从Cookie读取令牌并设置HTTP头:
// 从Cookie获取令牌
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// 设置XSRF-TOKEN头
fetch('/api/data', {
method: 'POST',
headers: {
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
'Content-Type': 'application/json'
},
body: JSON.stringify({/* data */})
});
3. 自动令牌注入
使用Spring Security标签库或Meta标签:
<meta name="_csrf" content="${_csrf.token}"/>
<meta name="_csrf_header" content="${_csrf.headerName}"/>
四、高级防护策略
1. 双重Cookie验证
http.csrf()
.csrfTokenRepository(new CookieCsrfTokenRepository())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler());
2. 同站点Cookie属性
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(
new CookieCsrfTokenRepository()::setCookieCustomizer);
}
// 自定义Cookie配置
CookieCsrfTokenRepository cookieRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
cookieRepo.setCookieCustomizer(c -> c.sameSite("Strict"));
3. 自定义令牌仓库
public class RedisCsrfTokenRepository implements CsrfTokenRepository {
private final RedisTemplate<String, String> redisTemplate;
@Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf",
UUID.randomUUID().toString());
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
// 存储到Redis并设置过期时间
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
// 从Redis获取令牌
}
}
五、测试与验证
1. 测试CSRF防护
@SpringBootTest
@AutoConfigureMockMvc
public class CsrfTests {
@Autowired
private MockMvc mockMvc;
@Test
public void shouldRejectRequestWithoutCsrfToken() throws Exception {
mockMvc.perform(post("/transfer")
.param("amount", "1000")
.param("account", "attacker"))
.andExpect(status().isForbidden());
}
@Test
public void shouldAcceptRequestWithValidCsrfToken() throws Exception {
mockMvc.perform(get("/form").with(csrf()))
.andDo(print())
.andExpect(status().isOk());
mockMvc.perform(post("/transfer")
.with(csrf()) // 自动添加有效令牌
.param("amount", "1000")
.param("account", "family"))
.andExpect(status().isOk());
}
}
2. 安全头检测
@Test
public void checkSecurityHeaders() throws Exception {
mockMvc.perform(get("/"))
.andExpect(header().exists("X-Content-Type-Options"))
.andExpect(header().exists("X-Frame-Options"))
.andExpect(header().exists("X-XSS-Protection"));
}
六、最佳实践与注意事项
- 防护范围:
- 保护所有状态修改请求(POST/PUT/DELETE/PATCH)
- 排除纯数据获取接口(GET/HEAD/OPTIONS)
- 性能考量:
- 分布式环境下使用共享令牌存储(如Redis)
- 避免频繁的令牌生成/验证操作
- 安全增强:
http.headers()
.contentSecurityPolicy("script-src 'self'")
.frameOptions().deny()
.xssProtection().block(true);
- 常见误区:
- 仅依赖CSRF防护而忽视XSS防护(XSS可窃取令牌)
- 在REST API中错误启用CSRF(应使用OAuth2等机制)
- 未正确处理令牌过期场景
- 现代替代方案:
- 使用SameSite Cookie属性(需浏览器支持)
- 实现基于加密的”双提交”模式
- 在API网关层统一处理CSRF防护
总结
Spring Security通过成熟的同步令牌模式为Web应用提供了可靠的CSRF防护,开发者应当:
- 理解默认防护机制的工作原理
- 根据应用架构选择合适的前端集成方案
- 在分布式环境中实现可靠的令牌存储
- 结合其他安全措施构建纵深防御体系
随着Web技术的发展,CSRF防护也需要与时俱进,结合SameSite Cookie、CORS策略等现代浏览器安全特性,构建更加完善的防护体系。
© 版权声明
文中内容均来源于公开资料,受限于信息的时效性和复杂性,可能存在误差或遗漏。我们已尽力确保内容的准确性,但对于因信息变更或错误导致的任何后果,本站不承担任何责任。如需引用本文内容,请注明出处并尊重原作者的版权。
THE END

























暂无评论内容