最新Spring Security实战教程(十一)CSRF攻防实战 - 从原理到防护的最佳实践

在这里插入图片描述

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
🌞《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

回顾链接:
最新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 的状态修改接口都构成威胁。本文从攻击原理入手,详细介绍了同步令牌双重提交 CookieSameSite 属性等防护方案,并给出了对应代码供小伙伴们参考!

希望这个章节的内容能够帮助小伙伴们更深入地理解 CSRF 的知识,在实际项目中设计出更灵活高效的安全策略。如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


下一章节:最新Spring Security实战教程(十二)CORS安全配置 - 跨域请求的安全边界设定

在这里插入图片描述

### Java Web 安全攻击与防御技术和最佳实践 #### 输入验证和数据过滤 为了防止恶意输入引发的安全漏洞,严格的输入验证必不可少。这不仅限于表单提交的数据,还包括任何来自客户端的信息。应定义明确的输入规则,并在服务器端执行这些规则以确保安全性[^1]。 ```java // 示例:使用正则表达式进行输入验证 public boolean isValidInput(String input) { String regex = "^[a-zA-Z0-9]+$"; // 只允许字母和数字 return Pattern.matches(regex, input); } ``` #### 认证与授权管理 实现强大的身份验证机制是保护Java Web应用的关键之一。推荐采用成熟的第三方安全框架来简化这一过程,比如Spring Security或Apache Shiro。这类框架提供了一系列开箱即用的功能和服务,能够有效减少自定义代码中的潜在风险[^3]。 ```xml <!-- Maven依赖配置 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` #### 防御SQL注入和XSS攻击 针对SQL注入,应当始终使用预编译语句代替动态拼接查询字符串;而对于XSS,则需对输出内容做适当转义处理,避免直接渲染未经过滤的内容给浏览器解析[^4]。 ```sql -- 正确做法:使用PreparedStatement防止SQL注入 String query = "SELECT * FROM users WHERE username=? AND password=?"; try (PreparedStatement pstmt = connection.prepareStatement(query)) { pstmt.setString(1, userInputUsername); pstmt.setString(2, hashedPassword); // 密码应该先哈希再比较 ResultSet rs = pstmt.executeQuery(); } // 输出时转义特殊字符预防XSS <script th:inline="javascript"> var userMessage = /*[[${message}]]*/ ''; document.write(userMessage.replace(/&/g,'&').replace(/</g,'<')); </script> ``` #### CSRF防护及其他高级话题 除了上述基本措施外,还需关注其他类型的攻击形式及其对应的缓解策略,例如跨站点请求伪造(CSRF),可以通过设置同源政策、引入一次性令牌等方式加以防范。此外,在设计阶段就考虑到会话管理和权限控制也是至关重要的方面[^5]。
评论 83
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Micro麦可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值