Spring Boot 3.x 开发中 CSRF 令牌在移动端 API 中的处理问题详解
目录
Spring Boot 3.x 开发中 CSRF 令牌在移动端 API 中的处理问题详解
引言
跨站请求伪造(CSRF)是一种经典的 Web 攻击方式,攻击者诱导用户在已登录的 Web 应用上执行非本意的操作。Spring Security 默认开启了 CSRF 防护,通过生成并验证 CSRF 令牌来确保请求来自可信的前端页面。然而,在移动端 API 场景中(如 iOS、Android 应用),由于 API 通常是无状态的、使用令牌认证(如 JWT),且不存在 Cookie 自动携带的上下文,CSRF 保护反而成为障碍,导致 API 调用失败(返回 403 Forbidden)。本文将深入剖析这一问题,并提供在 Spring Boot 3.x 中针对移动端 API 合理处理 CSRF 的多种方案。
1. 问题表现:移动端 API 遭遇 CSRF 拦截
当 Spring Boot 3.x 应用启用默认安全配置后,移动端 API 调用常出现以下现象:
- 现象 A:调用 POST、PUT、DELETE 等修改类接口时,返回
403 Forbidden,响应体包含"Expected CSRF token not found"或类似错误。 - 现象 B:即使移动端在请求头中携带了
X-CSRF-TOKEN,依然被拒绝,因为 Spring Security 默认期望令牌以参数或 Cookie 形式传递。 - 现象 C:移动端使用 Cookie 存储会话,但由于跨域或 Cookie 未携带,导致 CSRF 验证失败。
- 现象 D:API 请求在浏览器中测试正常,但在移动端(如 Postman)调用失败,引发调试困惑。
2. 原因分析:CSRF 机制与移动端 API 的冲突
2.1 CSRF 保护的原理
Spring Security 通过同步令牌模式防御 CSRF:
- 服务端生成一个随机令牌,并存入
HttpSession或 Cookie。 - 客户端在每次请求(尤其是状态变更请求)中携带该令牌(通常通过表单隐藏域、请求头
X-CSRF-TOKEN或 Cookie)。 - 服务端验证请求中的令牌与存储的令牌是否一致。
该机制依赖于浏览器自动携带 Cookie(包含会话标识)的特性,且通常结合表单渲染来嵌入令牌。
2.2 移动端 API 的特性
- 无状态认证:移动端多采用 Bearer Token(如 JWT)进行认证,通过
Authorization头传递,而非 Cookie。 - 无表单渲染:API 不返回 HTML 页面,无法通过隐藏字段携带 CSRF 令牌。
- 跨域请求:移动端可能从不同域名或 IP 发起请求,Cookie 默认不会跨域携带(除非设置
withCredentials和 CORS)。 - 缺乏会话上下文:若未启用 Session,则无法存储 CSRF 令牌;若启用 Session,又违背无状态设计初衷。
2.3 Spring Security 6.x 的默认行为
Spring Boot 3.x 中的 @EnableWebSecurity 默认开启了 CSRF 保护,且使用 CookieCsrfTokenRepository 将令牌存储在 Cookie 中。对于任何非 GET、HEAD、TRACE、OPTIONS 的请求,都会要求携带正确的令牌。移动端 API 如果不主动处理此令牌,就会被拦截。
3. 解决方案:针对移动端 API 的 CSRF 处理策略
根据应用的认证方式和安全需求,可以选择以下方案之一:
3.1 方案一:为移动端 API 禁用 CSRF(推荐)
如果移动端 API 使用 Bearer Token(如 JWT)认证,且该令牌已足够防御 CSRF(因为攻击者无法获取令牌),则可以直接禁用 CSRF 保护。这是最简单且最符合无状态 API 设计的做法。
配置示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 全局禁用 CSRF
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); // 或自定义 JWT 认证
return http.build();
}
}
如果只需对特定移动端 API 路径禁用 CSRF,可以使用 requestMatchers:
http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/mobile/**") // 移动端 API 路径忽略 CSRF
)
// 其他配置...
3.2 方案二:使用请求头传递 CSRF 令牌(仍保留保护)
若因安全合规要求必须保留 CSRF,可配置客户端通过请求头 X-CSRF-TOKEN 传递令牌,且 Spring Security 从请求头读取。
步骤:
- 确保移动端在登录后获取 CSRF 令牌(通常从 Cookie 或首次 GET 响应头获取)。
- 后续请求携带该令牌在请求头
X-CSRF-TOKEN中。
配置:Spring Security 默认支持从请求头 X-CSRF-TOKEN 读取令牌,无需额外配置。但需注意,令牌通常存储在 Cookie 中,移动端需要先请求一个端点获取令牌。
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 允许前端读取 Cookie
);
移动端获取令牌:
// 首次 GET 请求,响应头 Set-Cookie 中包含 XSRF-TOKEN
// 或通过 /csrf 端点获取
String csrfToken = response.getHeader("X-CSRF-TOKEN");
// 后续请求设置请求头
request.setHeader("X-CSRF-TOKEN", csrfToken);
3.3 方案三:自定义 CSRF 令牌存储与验证
对于无会话的无状态 API,可以使用基于 JWT 的 CSRF 令牌(如将令牌编码在 JWT 中),但实现复杂。更简单的做法是:如果使用 JWT,完全依赖 JWT 自身的签名和过期时间来防 CSRF(因为 JWT 通常需要显式携带,不会被浏览器自动发送)。
3.4 方案四:区分 Web 与移动端请求(基于请求头)
有时应用同时服务 Web 页面(需 CSRF)和移动端 API(不需 CSRF)。可以基于请求头(如 User-Agent 或自定义头 X-Requested-With)动态决定是否启用 CSRF。
实现:通过自定义 RequestMatcher 判断是否为移动端请求,然后动态禁用 CSRF。
public class MobileApiRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
return request.getRequestURI().startsWith("/api/mobile/");
}
}
// 配置
http
.csrf(csrf -> csrf
.requireCsrfProtectionMatcher(new NegatedRequestMatcher(new MobileApiRequestMatcher()))
);
或者更简单地,仅对非移动端路径启用 CSRF:
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.requireCsrfProtectionMatcher(new AndRequestMatcher(
new NotRequestMatcher(new AntPathRequestMatcher("/api/mobile/**")),
new DefaultCsrfRequestMatcher()
))
);
3.5 方案五:使用同源策略与 CORS 配合
如果移动端与 API 同域(如内网),可配置 CORS 允许携带 Cookie,并确保移动端请求时携带凭证。但此方案易受 CSRF 攻击,不推荐。
4. 完整示例:禁用 CSRF 并为移动端 API 配置 JWT 认证
假设应用使用 JWT 进行无状态认证,移动端 API 路径以 /api/ 开头。
4.1 安全配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 完全禁用 CSRF
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
);
return http.build();
}
private JwtAuthenticationConverter jwtAuthenticationConverter() {
// 自定义 JWT 转换器,提取权限
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix("ROLE_");
converter.setAuthoritiesClaimName("roles");
JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();
jwtConverter.setJwtGrantedAuthoritiesConverter(converter);
return jwtConverter;
}
}
4.2 测试控制器
@RestController
@RequestMapping("/api")
public class ApiController {
@PostMapping("/data")
public String postData(@RequestBody Map<String, String> data) {
return "Received: " + data;
}
}
移动端只需在请求头携带 Authorization: Bearer <JWT> 即可正常调用,无需任何 CSRF 令牌。
5. 最佳实践总结
- 优先禁用 CSRF:对于纯 API 服务(尤其是移动端),强烈建议禁用 CSRF 保护,依赖其他安全措施(如强认证、HTTPS、CORS)。
- 区分 Web 与 API:如果同一应用既提供 Web 页面又提供 API,应针对不同路径分别配置 CSRF(Web 启用,API 禁用)。
- 使用 JWT 等无状态认证:移动端推荐使用 Bearer Token,彻底规避 CSRF 风险。
- 谨慎处理 Cookie:如果必须使用 Cookie 存储会话,务必设置
SameSite=Strict或Lax,并配合 CSRF 令牌。 - 测试覆盖:确保在禁用 CSRF 后,所有移动端接口仍能正常调用,且不会被攻击者利用。
- 安全审计:在安全合规要求严格的场景(如金融、医疗),需评估禁用 CSRF 的合理性,必要时采用方案二保留保护。
6. 结语
CSRF 保护是 Web 应用的重要防线,但在移动端 API 场景中往往成为累赘。Spring Boot 3.x 提供了灵活的配置,允许开发者针对不同请求路径或认证方式选择是否启用 CSRF。通过合理配置,我们可以在保证安全的前提下,避免 CSRF 令牌给移动端 API 带来的不必要困扰。希望本文能帮助开发者在 Spring Boot 3.x 项目中正确处理 CSRF 与移动端 API 的集成问题,构建安全、高效的移动后端。
更多推荐

所有评论(0)