java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security CSRF防护与配置

Spring Security开启CSRF防护与基础配置的全过程

作者:知远漫谈

Spring Security 作为 Java 生态中最成熟、最广泛使用的安全框架,为开发者提供了强大的安全防护能力,其中,CSRF防护是其重要功能之一,本文将深入探讨 Spring Security 中 CSRF 防护的原理、配置方法以及最佳实践,需要的朋友可以参考下

引言

在当今的 Web 应用开发中,安全性已成为不可忽视的核心要素。随着网络攻击手段的不断演进,开发者必须采取多层次的安全措施来保护用户数据和系统完整性。Spring Security 作为 Java 生态中最成熟、最广泛使用的安全框架,为开发者提供了强大的安全防护能力。其中,CSRF(Cross-Site Request Forgery,跨站请求伪造)防护是其重要功能之一。

本文将深入探讨 Spring Security 中 CSRF 防护的原理、配置方法以及最佳实践,同时涵盖基础的安全配置,帮助开发者构建更加安全的 Web 应用程序。

什么是 CSRF 攻击?

CSRF(Cross-Site Request Forgery),中文称为跨站请求伪造,是一种常见的 Web 安全漏洞。攻击者利用用户在目标网站上的已认证会话,诱使用户在不知情的情况下执行非预期的操作。

CSRF 攻击的工作原理

让我们通过一个具体的例子来理解 CSRF 攻击:

假设你是一个银行系统的用户,已经登录到 bank.example.com。你的浏览器中保存了有效的会话 Cookie。此时,你又打开了一个恶意网站 evil-site.com,该网站包含以下 HTML 代码:

<img src="https://bank.example.com/transfer?to=attacker&amount=1000" width="0" height="0" />

当你访问这个恶意网站时,浏览器会自动向银行网站发送一个转账请求,因为你的浏览器会自动携带之前保存的会话 Cookie。由于银行网站无法区分这个请求是用户主动发起的还是被恶意网站诱导发起的,因此会执行转账操作。

CSRF 攻击的危害

CSRF 攻击可能导致以下严重后果:

根据 OWASP Top 10 的统计,CSRF 虽然在最新的 Top 10 中不再单独列出,但仍然是需要重点关注的安全问题。

Spring Security 的 CSRF 防护机制

Spring Security 默认启用了 CSRF 防护,这是基于安全最佳实践的考虑。其核心思想是:每个修改服务器状态的请求(如 POST、PUT、DELETE 等)都必须包含一个有效的 CSRF Token。

CSRF Token 的工作原理

默认的 CSRF 配置

在 Spring Security 5.x 及更高版本中,CSRF 防护默认是启用的。这意味着:

基础 Spring Security 配置

在深入讨论 CSRF 配置之前,让我们先建立一个基础的 Spring Security 配置。

依赖配置

首先,在 pom.xml 中添加必要的依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity6</artifactId>
    </dependency>
</dependencies>

基本安全配置类

创建一个基础的安全配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout(logout -> logout
                .permitAll()
            );
        // CSRF 防护默认启用,无需额外配置
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.builder()
            .username("user")
            .password("{noop}password")
            .roles("USER")
            .build();
            
        UserDetails admin = User.builder()
            .username("admin")
            .password("{noop}admin")
            .roles("ADMIN", "USER")
            .build();
            
        return new InMemoryUserDetailsManager(user, admin);
    }
}

这个配置实现了以下功能:

在 Thymeleaf 模板中使用 CSRF Token

当使用 Thymeleaf 作为模板引擎时,Spring Security 提供了便捷的方式来包含 CSRF Token。

基本表单示例

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <title>用户注册</title>
</head>
<body>
    <h2>用户注册</h2>
    <form th:action="@{/register}" method="post">
        <div>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="email">邮箱:</label>
            <input type="email" id="email" name="email" required>
        </div>
        <div>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <!-- CSRF Token 会自动包含 -->
        <input type="hidden" 
               th:name="${_csrf.parameterName}" 
               th:value="${_csrf.token}" />
        <button type="submit">注册</button>
    </form>
</body>
</html>

使用 Thymeleaf 安全标签

更简洁的方式是使用 Thymeleaf 的安全标签:

<form th:action="@{/register}" method="post" sec:csrf-token="true">
    <!-- 表单字段 -->
    <button type="submit">注册</button>
</form>

或者使用 th:action 的自动 CSRF 支持:

<form th:action="@{/register}" method="post">
    <!-- 表单字段 -->
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <button type="submit">注册</button>
</form>

在 JavaScript 应用中处理 CSRF Token

对于使用 AJAX 或现代前端框架(如 React、Vue.js)的应用,需要手动处理 CSRF Token。

获取 CSRF Token

Spring Security 提供了多种方式来获取 CSRF Token:

方式一:通过 Cookie

配置 Spring Security 将 CSRF Token 写入 Cookie:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

这样,CSRF Token 会被写入名为 XSRF-TOKEN 的 Cookie 中,前端 JavaScript 可以读取这个 Cookie:

// 从 Cookie 中读取 CSRF Token
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}
// 发送 AJAX 请求
fetch('/api/update', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
    },
    body: JSON.stringify({data: 'example'})
})
.then(response => response.json())
.then(data => console.log(data));

方式二:通过 Meta 标签

在 HTML 页面的 <head> 部分添加 Meta 标签:

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

然后在 JavaScript 中读取:

const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');
fetch('/api/update', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        [csrfHeader]: csrfToken
    },
    body: JSON.stringify({data: 'example'})
});

Axios 配置示例

如果你使用 Axios 作为 HTTP 客户端,可以进行全局配置:

// 从 Cookie 读取 CSRF Token
function getCookie(name) {
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
}
// 配置 Axios
axios.defaults.headers.common['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
// 或者使用拦截器
axios.interceptors.request.use(config => {
    const token = getCookie('XSRF-TOKEN');
    if (token) {
        config.headers['X-XSRF-TOKEN'] = token;
    }
    return config;
});

自定义 CSRF 配置选项

虽然 Spring Security 的默认 CSRF 配置适用于大多数场景,但在某些情况下,你可能需要自定义配置。

自定义 CSRF Token Repository

Spring Security 提供了多种 CSRF Token 存储方式:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                // 使用 HttpSession 存储(默认)
                .csrfTokenRepository(new HttpSessionCsrfTokenRepository())
                
                // 或者使用 Cookie 存储
                // .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                
                // 自定义 Token 名称
                .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
            )
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            );
        return http.build();
    }
}

自定义 CSRF Token 名称

你可以自定义 CSRF Token 的参数名称和 Header 名称:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf
            .csrfTokenRepository(new HttpSessionCsrfTokenRepository())
            .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
        )
        .authorizeHttpRequests(authz -> authz
            .anyRequest().authenticated()
        );
    return http.build();
}

// 自定义 CsrfTokenRequestHandler
public class CustomCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, 
                      Supplier<CsrfToken> csrfToken) {
        super.handle(request, response, csrfToken);
        // 自定义逻辑
    }
}

CSRF 匹配器配置

你可以指定哪些请求需要 CSRF 防护:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf
            .requireCsrfProtectionMatcher(
                new AndRequestMatcher(
                    CsrfFilter.DEFAULT_CSRF_MATCHER,
                    new NegatedRequestMatcher(new AntPathRequestMatcher("/api/**"))
                )
            )
        )
        .authorizeHttpRequests(authz -> authz
            .anyRequest().authenticated()
        );
    return http.build();
}

REST API 中的 CSRF 处理

对于纯 REST API 应用,CSRF 防护的处理方式有所不同。

无状态 API 的 CSRF 考虑

如果您的 REST API 是完全无状态的(使用 JWT Token 而不是 Session 进行认证),那么 CSRF 攻击实际上不会发生,因为:

  1. 客户端不会自动发送认证凭证(JWT Token 需要手动添加到请求头)
  2. 没有会话 Cookie 可以被浏览器自动携带

在这种情况下,可以禁用 CSRF 防护:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // 禁用 CSRF
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

混合应用的处理

如果您的应用既有传统的 Web 页面,又有 REST API,可以针对不同路径进行不同的 CSRF 配置:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    @Order(1)
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            );
        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/web/**", "/")
            .authorizeHttpRequests(authz -> authz
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        // CSRF 默认启用
        return http.build();
    }
}

常见的 CSRF 配置错误和解决方案

在实际开发中,开发者经常会遇到一些 CSRF 相关的问题。以下是常见问题及其解决方案。

错误 1:403 Forbidden 错误

问题描述:提交表单时出现 403 Forbidden 错误。

原因分析

解决方案

确保表单中包含 CSRF Token:

<!-- Thymeleaf -->
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<!-- JSP -->
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

错误 2:AJAX 请求失败

问题描述:AJAX 请求返回 403 错误。

解决方案

配置 CSRF Token 通过 Cookie 传递:

.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)

并在 AJAX 请求中包含 Token:

// jQuery 示例
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
            xhr.setRequestHeader("X-XSRF-TOKEN", getCookie("XSRF-TOKEN"));
        }
    }
});

错误 3:文件上传失败

问题描述:使用 multipart/form-data 表单上传文件时 CSRF 验证失败。

原因分析:Spring Security 的 CSRF 过滤器在处理 multipart 请求时可能无法正确解析表单数据。

解决方案

确保 MultipartResolver 在 CsrfFilter 之前配置:

@Configuration
public class WebConfig {
    
    @Bean
    public MultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxUploadSize(10485760); // 10MB
        return resolver;
    }
}

或者在 Spring Boot 中,确保使用正确的配置:

# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

CSRF 防护的最佳实践

为了确保应用程序的安全性,以下是 CSRF 防护的最佳实践:

1. 始终启用 CSRF 防护

除非你的应用是完全无状态的 REST API,否则应该始终启用 CSRF 防护。

// 好的做法 - 默认启用
http.csrf(Customizer.withDefaults());

// 避免的做法 - 除非必要,不要禁用
http.csrf(csrf -> csrf.disable());

2. 正确处理所有修改状态的请求

确保所有修改服务器状态的请求(POST、PUT、PATCH、DELETE)都包含有效的 CSRF Token。

3. 使用 SameSite Cookie 属性

配置会话 Cookie 的 SameSite 属性为 LaxStrict

@Bean
public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
    CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    repository.setCookieName("XSRF-TOKEN");
    repository.setHeaderName("X-XSRF-TOKEN");
    return repository;
}

@Bean
public CookieSameSiteSupplier cookieSameSiteSupplier() {
    return CookieSameSiteSupplier.ofStrict();
}

4. 定期轮换 CSRF Token

虽然 Spring Security 会自动处理 Token 的生命周期,但了解其工作机制很重要。CSRF Token 通常与用户会话绑定,会话过期时 Token 也会失效。

5. 结合其他安全措施

CSRF 防护应该与其他安全措施结合使用:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(Customizer.withDefaults())
        .authorizeHttpRequests(authz -> authz
            .anyRequest().authenticated()
        )
        .headers(headers -> headers
            .frameOptions().deny() // 防止点击劫持
            .contentTypeOptions().and() // 防止 MIME 类型嗅探
            .xssProtection().and() // XSS 防护
        )
        .sessionManagement(session -> session
            .maximumSessions(1) // 限制会话数量
            .maxSessionsPreventsLogin(true)
        );
    return http.build();
}

高级 CSRF 配置场景

在复杂的应用场景中,可能需要更高级的 CSRF 配置。

多租户应用中的 CSRF 处理

在多租户应用中,可能需要为不同的租户使用不同的 CSRF 配置:

@Component
public class MultiTenantCsrfTokenRepository implements CsrfTokenRepository {
    
    private final Map<String, CsrfTokenRepository> tenantRepositories = new ConcurrentHashMap<>();
    
    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        String tenantId = getTenantId(request);
        return getRepository(tenantId).generateToken(request);
    }
    
    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        String tenantId = getTenantId(request);
        getRepository(tenantId).saveToken(token, request, response);
    }
    
    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        String tenantId = getTenantId(request);
        return getRepository(tenantId).loadToken(request);
    }
    
    private CsrfTokenRepository getRepository(String tenantId) {
        return tenantRepositories.computeIfAbsent(tenantId, k -> {
            // 为每个租户创建独立的存储库
            return new HttpSessionCsrfTokenRepository();
        });
    }
    
    private String getTenantId(HttpServletRequest request) {
        // 从请求中提取租户 ID
        return request.getHeader("X-Tenant-ID");
    }
}

分布式系统中的 CSRF Token 共享

在分布式系统中,用户的请求可能被路由到不同的服务器实例。这时需要确保 CSRF Token 在所有实例间共享:

@Component
public class RedisCsrfTokenRepository implements CsrfTokenRepository {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final String tokenKeyPrefix = "csrf:token:";
    
    public RedisCsrfTokenRepository(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        String tokenValue = UUID.randomUUID().toString();
        String tokenKey = tokenKeyPrefix + request.getSession().getId();
        
        DefaultCsrfToken token = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", tokenValue);
        
        // 存储到 Redis,设置过期时间
        redisTemplate.opsForValue().set(tokenKey, tokenValue, 30, TimeUnit.MINUTES);
        
        return token;
    }
    
    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        if (token == null) {
            String tokenKey = tokenKeyPrefix + request.getSession().getId();
            redisTemplate.delete(tokenKey);
        } else {
            generateToken(request); // 重新生成并存储
        }
    }
    
    @Override
    public CsrfToken loadToken(HttpServletRequest request) {
        String tokenKey = tokenKeyPrefix + request.getSession().getId();
        String tokenValue = (String) redisTemplate.opsForValue().get(tokenKey);
        
        if (tokenValue != null) {
            return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", tokenValue);
        }
        return null;
    }
}

然后在安全配置中使用:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, 
                                     RedisCsrfTokenRepository csrfTokenRepository) throws Exception {
    http
        .csrf(csrf -> csrf
            .csrfTokenRepository(csrfTokenRepository)
        )
        .authorizeHttpRequests(authz -> authz
            .anyRequest().authenticated()
        );
    return http.build();
}

测试 CSRF 防护

编写测试用例来验证 CSRF 防护是否正常工作是非常重要的。

单元测试示例

@SpringBootTest
@AutoConfigureTestDatabase
class CsrfSecurityTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldRejectPostWithoutCsrfToken() throws Exception {
        mockMvc.perform(post("/api/data")
                .content("{\"name\":\"test\"}")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isForbidden());
    }

    @Test
    void shouldAcceptPostWithValidCsrfToken() throws Exception {
        mockMvc.perform(post("/api/data")
                .with(csrf()) // 自动添加 CSRF Token
                .content("{\"name\":\"test\"}")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }

    @Test
    void shouldAllowGetRequests() throws Exception {
        mockMvc.perform(get("/api/data"))
                .andExpect(status().isOk());
    }
}

集成测试示例

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationCsrfTest {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void testCsrfProtection() {
        // 尝试不带 CSRF Token 的 POST 请求
        ResponseEntity<String> response = restTemplate.postForEntity(
            "http://localhost:" + port + "/api/data",
            "{\"name\":\"test\"}",
            String.class
        );
        
        // 应该返回 403
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
    }
}

性能考虑和优化

虽然 CSRF 防护增加了安全性,但也可能带来一些性能开销。以下是一些优化建议:

1. 合理的 Token 存储策略

选择合适的 Token 存储策略:

// 对于高并发应用,考虑使用 Cookie 存储
.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)

2. 缓存 CSRF Token

对于频繁的 AJAX 请求,可以缓存 CSRF Token 以避免重复获取:

// 缓存 CSRF Token
let cachedCsrfToken = null;

function getCsrfToken() {
    if (!cachedCsrfToken) {
        cachedCsrfToken = getCookie('XSRF-TOKEN');
    }
    return cachedCsrfToken;
}

// 在每次请求后刷新缓存(可选)
function refreshCsrfToken() {
    cachedCsrfToken = null;
}

3. 异步 Token 刷新

在长时间运行的应用中,CSRF Token 可能会过期。可以实现异步刷新机制:

// 拦截 403 错误并尝试刷新 Token
axios.interceptors.response.use(
    response => response,
    error => {
        if (error.response.status === 403) {
            // 尝试获取新的页面来刷新 Token
            return axios.get('/refresh-csrf')
                .then(() => {
                    // 重试原始请求
                    return axios.request(error.config);
                });
        }
        return Promise.reject(error);
    }
);

与其他安全框架的对比

了解 Spring Security 的 CSRF 防护与其他框架的差异有助于做出更好的技术选择。

Spring Security vs Django CSRF

Django 也提供了强大的 CSRF 防护,默认启用且配置简单。两者的主要区别:

Spring Security vs Express.js (CSRF)

Node.js 的 Express.js 框架通过 csurf 中间件提供 CSRF 防护:

// Express.js 示例
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
    res.render('send', { csrfToken: req.csrfToken() });
});

相比之下,Spring Security 的优势在于:

实际案例分析

让我们通过一个实际的电商应用案例来展示 CSRF 防护的重要性。

场景描述

假设我们有一个电商网站,用户可以:

安全配置

@Configuration
@EnableWebSecurity
public class EcommerceSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            )
            .authorizeHttpRequests(authz -> authz
                .requestMatchers("/", "/products/**", "/static/**").permitAll()
                .requestMatchers("/cart/**", "/checkout/**", "/profile/**")
                    .authenticated()
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .defaultSuccessUrl("/dashboard")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
                .permitAll()
            );
        return http.build();
    }
}

前端实现

<!-- 购物车页面 -->
<form id="addToCartForm" th:action="@{/cart/add}" method="post">
    <input type="hidden" name="productId" th:value="${product.id}" />
    <input type="hidden" name="quantity" value="1" />
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <button type="submit">加入购物车</button>
</form>
<script>
// AJAX 添加到购物车
document.getElementById('addToCartForm').addEventListener('submit', function(e) {
    e.preventDefault();
    const formData = new FormData(this);
    fetch('/cart/add', {
        method: 'POST',
        headers: {
            'X-XSRF-TOKEN': getCookie('XSRF-TOKEN')
        },
        body: formData
    })
    .then(response => {
        if (response.ok) {
            updateCartCount();
        }
    });
});
</script>

攻击防护效果

有了 CSRF 防护后,即使攻击者构造了恶意表单:

<!-- 恶意网站上的代码 -->
<form action="https://legitimate-ecommerce.com/cart/add" method="post">
    <input type="hidden" name="productId" value="123" />
    <input type="hidden" name="quantity" value="100" />
    <!-- 缺少有效的 CSRF Token -->
    <input type="submit" value="Click me!" />
</form>

由于缺少有效的 CSRF Token,服务器会拒绝这个请求,从而保护了用户的购物车不被恶意操作。

未来发展趋势和新特性

Spring Security 不断演进,未来的 CSRF 防护可能会包含以下新特性:

1. 更智能的 Token 管理

未来的版本可能会提供更智能的 Token 管理机制,包括:

2. 与现代 Web 标准的集成

随着 Web 标准的发展,Spring Security 可能会更好地集成:

3. 自动化安全检测

未来的版本可能会包含自动化安全检测功能,能够:

总结和建议 

CSRF 防护是 Web 应用安全的重要组成部分。Spring Security 提供了强大而灵活的 CSRF 防护机制,开发者应该充分利用这些功能来保护应用程序。

关键要点回顾

  1. 默认启用:Spring Security 默认启用 CSRF 防护,这是安全最佳实践
  2. 正确集成:在前端模板和 JavaScript 应用中正确集成 CSRF Token
  3. 合理配置:根据应用类型(传统 Web 应用 vs REST API)选择合适的配置
  4. 全面测试:编写充分的测试用例验证 CSRF 防护的有效性
  5. 持续关注:关注安全最佳实践和 Spring Security 的新特性

最终建议

通过正确配置和使用 Spring Security 的 CSRF 防护功能,你可以大大降低应用程序遭受 CSRF 攻击的风险,为用户提供更安全的使用体验。

以上就是Spring Security开启CSRF防护与基础配置的全过程的详细内容,更多关于Spring Security CSRF防护与配置的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文