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风险。