🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
最新Spring Security实战教程(十一)CSRF攻防实战 - 从原理到防护的最佳实践
- 1. 前言
- 2. CSRF 攻击原理
- 2.1 攻击原理图解
- 2.2 攻击示例
- 3. Spring Security防御机制解析
- 3. 1 同步令牌模式(Synchronizer Token Pattern)
- 3. 2 双重提交 Cookie(Double Submit Cookie)
- 3. 3 SameSite Cookie 属性
- 4. 实战代码示例
- 4.1 在 Spring Security 中启用 CSRF 防护
- ❶ Thymeleaf 模板中集成
- ❷ 前后端分离适配方案
- ❸ 自定义令牌存储策略
- 4.2 双重Cookie验证
- 4.3 SameSite Cookie策略
- 结语
回顾链接:
最新Spring Security实战教程(一)初识Spring Security安全框架
最新Spring Security实战教程(二)表单登录定制到处理逻辑的深度改造
最新Spring Security实战教程(三)Spring Security 的底层原理解析
最新Spring Security实战教程(四)基于内存的用户认证
最新Spring Security实战教程(五)基于数据库的动态用户认证传统RBAC角色模型实战开发
最新Spring Security实战教程(六)最新Spring Security实战教程(六)基于数据库的ABAC属性权限模型实战开发
最新Spring Security实战教程(七)方法级安全控制@PreAuthorize注解的灵活运用
最新Spring Security实战教程(八)Remember-Me实现原理 - 持久化令牌与安全存储方案
最新Spring Security实战教程(九)前后端分离认证实战 - JWT+SpringSecurity无缝整合
最新Spring Security实战教程(十)权限表达式进阶 - 在SpEL在安全控制中的高阶魔法
专栏更新完毕后,博主将会上传所有章节代码到CSDN资源免费给大家下载,如你不想等后续章节代码需提前获取,可以私信或留言!
1. 前言
在前面学习的章节中,相信大家一定看一个配置 .csrf()
, 回忆一下之前使用 Spring Security
默认页登录的时候,该配置 Spring Security
默认开启,主要做用于 CSRF
防护, 如果你现在还不了解什么是 CSRF 防护,没关系通过本章节,博主带着大家一起深入学习这个知识点~
2. CSRF 攻击原理
跨站请求伪造(CSRF
)是一种利用受信任用户的身份,诱使用户在已登录的应用中执行非预期操作的攻击手段。
当用户在某个站点(如银行)登录并持有有效 Session Cookie
后,攻击者可通过精心构造的请求(例如隐藏在图片或表单中的 POST 请求)在用户不知情的情况下向该站点发起请求,并携带用户的 Cookie,从而完成诸如转账、修改邮箱等敏感操作。
2.1 攻击原理图解
用户访问了A站点,获得了Session或Cookie后,
用户不经意间访问到了恶意网站,此刻恶意网站伪造对A站点的危险请求
2.2 攻击示例
下面示例展示了一个最常见的 CSRF
攻击场景:用户登录了 https://bank.com 后,攻击者在自己的网站 https://evil.com 上放置如下 HTML 片段:
<!-- 在恶意页面上渲染时,立即向 bank.com 发起转账请求 -->
<img src="https://bank.com/transfer?amount=1000&to=attacker" />
3. Spring Security防御机制解析
3. 1 同步令牌模式(Synchronizer Token Pattern)
同步令牌模式是 Spring Security
的默认方案, 服务器在渲染每个需要保护的表单页面时,向用户 Session
中存入一个随机生成的 Token
,并在表单中以隐藏字段输出;提交时,服务器验证该字段与 Session
中的 Token
是否一致,若不匹配则拒绝请求。此模式能有效防止 CSRF
攻击,因为攻击者无法从第三方域读取到该随机 Token
。
核心防御流程
- 服务端生成随机Token(每个Session唯一)
- Token嵌入HTML表单的隐藏字段或HTTP头
- 客户端提交请求时必须携带有效Token
- 服务端校验Token合法性
3. 2 双重提交 Cookie(Double Submit Cookie)
服务器在首次响应页面时,通过 Set-Cookie
设置一个随机 Token
,同时在页面中通过脚本将该 Token
读出并写入一个请求头(或隐藏表单字段)。服务器接收请求后,比较 Cookie 中的 Token 与请求中携带的 Token 是否一致。由于浏览器同源策略不能让第三方域读取 Cookie,攻击者无法同步两个值。
3. 3 SameSite Cookie 属性
浏览器支持在 Set-Cookie
响应头中声明 SameSite
属性,用来限制 Cookie
在跨站请求时是否发送。设置为 Strict 或 Lax 模式,可从源头上阻止大部分 CSRF 请求。
- SameSite=Strict:绝不在第三方请求中发送该 Cookie;
- SameSite=Lax:仅允许在“安全”的跨站 GET 导航中发送。
4. 实战代码示例
这里我们将针对上述三种防护机制,进行相关代码演示
4.1 在 Spring Security 中启用 CSRF 防护
Spring Security
默认开启 CSRF
保护,采用的是同步令牌模式。下面展示如何单体、前后分离中集成
❶ Thymeleaf 模板中集成
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf// 可自定义 CsrfTokenRepository,例如 CookieCsrfTokenRepository.withHttpOnlyFalse()).authorizeHttpRequests(auth -> auth.anyRequest().authenticated()).formLogin(withDefaults());return http.build();}
}
在 Thymeleaf 页面中添加隐藏字段,设置Token
<!-- Thymeleaf 模板:form.html -->
<form th:action="@{/transfer}" method="post"><!-- 输出 CSRF 隐藏字段 --><input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /><input type="number" name="amount" /><button type="submit">Transfer</button>
</form>
在控制器中,Spring Security
自动会在每次 POST
请求时校验表单中的 ${_csrf.token}
与 Session
中的令牌是否匹配,
不匹配则抛出InvalidCsrfTokenException
❷ 前后端分离适配方案
下面演示在前后分离中的适配,前端在请求前 初始化时获取CSRF Token
// 自定义CSRF令牌处理器
public class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {// 将CSRF Token暴露给前端JavaScriptCsrfToken token = csrfToken.get();if (token != null) {response.setHeader(token.getHeaderName(), token.getToken());}}
}//SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()))// 其他配置...return http.build();}
}
❸ 自定义令牌存储策略
// 使用Redis存储CSRF令牌(分布式场景)
@Bean
public CsrfTokenRepository redisCsrfTokenRepository(RedisTemplate<String, String> redisTemplate) {return new CsrfTokenRepository() {@Overridepublic CsrfToken generateToken(HttpServletRequest request) {return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", UUID.randomUUID().toString());}@Overridepublic void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {String sessionId = request.getSession().getId();if (token == null) {redisTemplate.delete(sessionId);} else {redisTemplate.opsForValue().set(sessionId, token.getToken(), 30, MINUTES);}}@Overridepublic CsrfToken loadToken(HttpServletRequest request) {String sessionId = request.getSession().getId();String token = redisTemplate.opsForValue().get(sessionId);return token != null ? new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token) : null;}};
}
前端演示代码
// 初始化时获取CSRF Token
fetch('/csrf', { credentials: 'include' }).then(res => {const token = res.headers.get('X-CSRF-TOKEN');axios.defaults.headers.common['X-CSRF-TOKEN'] = token;});// 所有POST请求自动携带Token
axios.interceptors.request.use(config => {if (['post', 'put', 'delete'].includes(config.method.toLowerCase())) {config.headers['X-CSRF-TOKEN'] = getCSRFToken(); }return config;
});
4.2 双重Cookie验证
实际上在我们日常开发中,使用 Spring Security
同步令牌方案,基本能满足我们大部分需求,这里就简单演示一下双重Cookie验证
public class DoubleCookieCsrfFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());if (requiresValidation(request)) {String headerToken = request.getHeader(token.getHeaderName());String cookieToken = getCookieValue(request, "CSRF-TOKEN");if (!token.getToken().equals(headerToken) || !token.getToken().equals(cookieToken)) {response.sendError(HttpStatus.FORBIDDEN.value());return;}}filterChain.doFilter(request, response);}private boolean requiresValidation(HttpServletRequest request) {return "POST".equalsIgnoreCase(request.getMethod()) ||"PUT".equalsIgnoreCase(request.getMethod()) ||"DELETE".equalsIgnoreCase(request.getMethod());}
}
4.3 SameSite Cookie策略
限制cookie的跨站请求
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())).sessionManagement(session -> session.sessionCookiePolicy(cookie -> cookie.sameSite(SameSite.STRICT)));return http.build();
}
结语
CSRF
攻击凭借“利用用户身份” 的特点,对任何依赖 Cookie 的状态修改接口都构成威胁。本文从攻击原理入手,详细介绍了同步令牌
、双重提交 Cookie
、SameSite 属性
等防护方案,并给出了对应代码供小伙伴们参考!
希望这个章节的内容能够帮助小伙伴们更深入地理解 CSRF
的知识,在实际项目中设计出更灵活高效的安全策略。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!
下一章节:最新Spring Security实战教程(十二)CORS安全配置 - 跨域请求的安全边界设定