---
#### **一、CSRF攻击原理与危害**
**CSRF(跨站请求伪造)** 是一种利用用户已认证身份发起恶意请求的攻击方式。攻击者诱导用户访问恶意页面,该页面携带伪造请求(如转账、修改密码)发送至目标网站,由于浏览器自动携带用户的认证Cookie,服务端可能误认为是合法操作。
**典型攻击流程**:
1. 用户登录信任网站A,获取会话Cookie;
2. 用户访问恶意网站B,触发对网站A的请求(如隐藏表单或图片);
3. 浏览器自动携带Cookie,请求被网站A视为合法操作。
**防御核心**:验证请求是否来自合法源,并携带可信凭证(如CSRF Token)。
---
#### **二、Spring Security的CSRF防护机制**
Spring Security通过 **令牌验证** 和 **安全属性配置** 实现CSRF防护,核心组件包括`CsrfToken`、`CsrfTokenRepository`和`CsrfFilter`。
##### **1. CSRF Token的生成与验证**
• **生成规则**:服务端为每个会话生成唯一随机Token(如UUID),存储于Session或Cookie中。
• **存储方式**:
• **Session存储**:默认使用`HttpSessionCsrfTokenRepository`,Token与用户会话绑定。
• **Cookie存储**:通过`CookieCsrfTokenRepository`写入客户端,需设置`SameSite`和`Secure`属性增强安全性。
• **验证流程**:
客户端提交请求时需携带Token(表单参数或请求头),服务端比对请求中的Token与存储值,不一致则拒绝请求。
**配置示例**:
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(c -> c.csrfTokenRepository(cookieCsrfTokenRepository()))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED));
return http.build();
}
private CsrfTokenRepository cookieCsrfTokenRepository() {
CookieCsrfTokenRepository repo = CookieCsrfTokenRepository.withHttpOnlyFalse();
repo.setCookieDomain("example.com");
repo.setSameSite("Lax"); // 支持跨站安全请求
return repo;
}
}
```
##### **2. SameSite属性增强防御**
• **作用**:限制Cookie的跨站传递,防止恶意站点利用用户Cookie发起请求。
• `SameSite=Strict`:完全禁止跨站携带Cookie,适用于敏感操作(如支付)。
• `SameSite=Lax`:允许安全的跨站请求(如导航类GET请求),平衡安全性与用户体验。
• **配置**:
```java
ResponseCookie cookie = ResponseCookie.from("XSRF-TOKEN", token)
.sameSite("Lax")
.secure(true)
.httpOnly(true)
.build();
```
##### **3. CsrfFilter的工作机制**
`CsrfFilter`是Spring Security处理CSRF的核心过滤器,其流程如下:
1. **Token加载**:从`CsrfTokenRepository`加载当前会话的Token,若不存在则生成并存储。
2. **请求放行**:对安全方法(GET、HEAD等)直接放行,不验证Token。
3. **Token校验**:对非安全方法(POST、PUT等),检查请求参数或头中的Token是否匹配,否则返回403。
**源码关键逻辑**:
```java
public class CsrfFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
CsrfToken csrfToken = tokenRepository.loadToken(request);
if (csrfToken == null) {
csrfToken = tokenRepository.generateToken(request);
tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
if (!requiresCsrfProtection(request)) {
chain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
response.sendError(HttpStatus.FORBIDDEN.value());
return;
}
chain.doFilter(request, response);
}
}
```
---
#### **三、分布式场景下的CSRF防护:Spring Session与Redis集成**
在微服务架构中,会话数据需跨服务共享,传统Session存储无法满足需求。**Spring Session** 通过将会话数据存储到Redis,实现分布式一致性。
##### **1. 集成Spring Session与Redis**
• **依赖引入**:
```xml
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
```
• **配置Redis连接**:
```yaml
spring:
redis:
host: localhost
port: 6379
session:
store-type: redis
```
• **启用Redis存储**:
```java
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
```
##### **2. CSRF Token的分布式存储**
• **自动同步**:使用`HttpSessionCsrfTokenRepository`时,Token随Session自动存入Redis,各服务节点通过Session ID读取验证。
• **性能优化**:设置合理的TTL(如30分钟),避免Redis存储膨胀。
---
#### **四、最佳实践与安全增强建议**
1. **分层防御策略**:
• 敏感操作启用`SameSite=Strict`和二次验证(如短信验证码)。
• 非敏感操作使用`SameSite=Lax`,兼顾安全与体验。
2. **HTTPS强制**:所有涉及Cookie和Token的通信启用HTTPS,防止中间人攻击。
3. **监控与审计**:记录CSRF验证失败的请求,分析潜在攻击行为。
4. **兼容性处理**:对不支持SameSite的旧浏览器,回退至Token验证为主。
---
#### **五、总结**
Spring Security通过 **CSRF Token验证**、**SameSite属性** 和 **分布式会话管理** 构建了多层次的防护体系。开发者需根据业务场景灵活选择存储方式(Session/Cookie)、配置安全属性,并结合Spring Session实现高可用架构。防御CSRF不仅是技术问题,更需在架构设计、监控响应等环节形成闭环,持续提升应用安全性。
**参考资料**: