🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
最新Spring Security实战教程(十一)CSRF攻防实战 - 从原理到防护的最佳实践
回顾链接:
最新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 {
@Bean
public 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 {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
Supplier<CsrfToken> csrfToken) {
// 将CSRF Token暴露给前端JavaScript
CsrfToken token = csrfToken.get();
if (token != null) {
response.setHeader(token.getHeaderName(), token.getToken());
}
}
}
//SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public 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() {
@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) {
String sessionId = request.getSession().getId();
if (token == null) {
redisTemplate.delete(sessionId);
} else {
redisTemplate.opsForValue().set(sessionId, token.getToken(), 30, MINUTES);
}
}
@Override
public 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 {
@Override
protected 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安全配置 - 跨域请求的安全边界设定