1. 概述

上一篇文章中,我们解释了CSRF攻击如何影响Spring MVC应用。本文将深入分析不同场景,判断无状态REST API是否可能遭受CSRF攻击,以及如何防护。

2. REST API需要CSRF防护吗?

首先,在专项指南中可以找到CSRF攻击的示例。阅读后可能认为:由于服务器端没有会话可窃取,无状态REST API不会受此类攻击影响。

但关键点在于客户端如何存储和发送凭证。我们以典型场景为例:Spring REST API应用 + JavaScript客户端。客户端使用安全令牌(如JSESSIONID或JWT)作为凭证,该令牌在用户成功登录后由REST API颁发。

2.1 凭证不持久化

从REST API获取令牌后,可将其设为JavaScript全局变量。这将令牌保存在浏览器内存中,仅对当前页面有效

这是最安全的方式:CSRF和XSS攻击总会在新页面打开客户端应用,无法访问登录页面的内存。

但用户体验极差:每次访问或刷新页面都需要重新登录。在移动端浏览器中,即使切换到后台,系统也会清除内存,导致频繁重新登录。因此该方案很少采用。

2.2 凭证存储在浏览器存储中

可将令牌持久化到浏览器存储(如session storage)。JavaScript客户端读取令牌后,在所有REST请求的Authorization头中发送。

这是JWT的常见用法:实现简单且能防御CSRF攻击。与Cookie不同,浏览器存储变量不会自动发送到服务器。

但存在XSS漏洞:恶意JavaScript代码可访问浏览器存储并窃取令牌。此时必须保护应用

2.3 凭证存储在Cookie中

另一种方案是使用Cookie存储凭证。应用漏洞取决于Cookie的使用方式:

  • 仅存储凭证(如JWT)但不用于认证
    JavaScript客户端需读取令牌并在Authorization头发送。
    此时不受CSRF影响:即使恶意请求自动携带Cookie,REST API仍从Authorization头读取凭证。但需将HTTP-only标志设为false以允许客户端读取Cookie。
    但会引入XSS漏洞(与2.2节相同)。

  • 通过会话Cookie认证(HTTP-only=true
    典型如Spring Security的JSESSIONID。为保持API无状态,服务器端绝不能使用会话。
    此时与传统有状态应用一样存在CSRF漏洞:Cookie会自动附加到所有REST请求,点击恶意链接即可执行认证操作。

2.4 其他易受CSRF攻击的配置

某些不使用安全令牌的配置也可能存在CSRF漏洞,例如:

这些方案虽不常见,但存在相同缺陷:浏览器会自动发送凭证到所有HTTP请求。此时必须启用CSRF防护。

3. 在Spring Boot中禁用CSRF防护

Spring Security 4.x版本后默认启用CSRF防护。若项目不需要,可在SecurityFilterChain Bean中禁用:

@Configuration
public class SpringBootSecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        return http.build();
    }
}

4. 为REST API启用CSRF防护

4.1 Spring配置

若需要CSRF防护,可在SecurityFilterChain中使用CookieCsrfTokenRepository通过Cookie发送CSRF令牌。必须设置HTTP-only=false以便JavaScript客户端读取:

@Configuration
public class SpringSecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
        return http.build();
    }
}

重启应用后,请求会返回HTTP错误,表明CSRF防护已生效。将日志级别设为DEBUG可确认错误来自CsrfFilter

Invalid CSRF token found for http://...

浏览器中会出现新的XSRF-TOKEN Cookie。在REST控制器中添加以下代码记录令牌信息:

CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
LOGGER.info("{}={}", token.getHeaderName(), token.getToken());

4.2 客户端配置

首次访问API后,客户端会设置XSRF-TOKEN Cookie。用JavaScript正则表达式提取:

const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');

所有修改状态的请求(POST/PUT/DELETE/PATCH)都必须携带该令牌。Spring期望在X-XSRF-TOKEN头中接收,使用Fetch API设置:

fetch(url, {
    method: 'POST',
    body: JSON.stringify({ /* data to send */ }),
    headers: { 'X-XSRF-TOKEN': csrfToken },
})

现在请求可正常执行,REST API日志中的"Invalid CSRF token"错误消失。攻击者无法执行CSRF攻击:钓鱼网站尝试相同请求时,若用户未先访问真实网站,Cookie未设置,请求会失败。

5. 结论

本文分析了REST API在不同场景下是否可能遭受CSRF攻击,并演示了如何使用Spring Security启用或禁用CSRF防护。核心原则是:凭证存储方式决定漏洞类型——内存存储最安全但体验差,Cookie存储需权衡CSRF与XSS风险。


原始标题:CSRF With Stateless REST API