Spring Security CSRF与CORS详解
一、知识概述
CSRF(跨站请求伪造)和 CORS(跨源资源共享)是 Web 安全中的两个重要概念。Spring Security 提供了对这两种安全机制的完善支持,帮助开发者构建安全的 Web 应用。
CSRF 防护和 CORS 配置的核心概念:
- CSRF:防止恶意网站冒充用户发起请求
- CORS:控制浏览器跨域访问权限
- 同源策略:浏览器安全限制
- 预检请求:CORS 的 OPTIONS 请求
理解 CSRF 和 CORS 的原理,是构建安全 Web 应用的必要知识。
二、知识点详细讲解
2.1 CSRF 原理
正常请求:
用户 → 浏览器 → 目标网站(已登录)→ 执行操作
CSRF 攻击:
用户 → 恶意网站 → 浏览器(携带目标网站 Cookie)→ 目标网站 → 执行恶意操作
CSRF 防护原理
1. 服务端生成 CSRF Token
2. Token 嵌入表单或响应头
3. 用户提交请求时携带 Token
4. 服务端验证 Token 有效性
5. Token 不匹配则拒绝请求
2.2 CORS 原理
浏览器同源策略:
- 相同协议(http/https)
- 相同域名
- 相同端口
跨域请求:
前端(http://localhost:3000)→ 后端(http://localhost:8080)
CORS 流程
简单请求:
1. 浏览器发送请求,携带 Origin 头
2. 服务端检查 Origin,返回 Access-Control-Allow-Origin
3. 浏览器检查响应头,决定是否允许
预检请求:
1. 浏览器发送 OPTIONS 预检请求
2. 服务端返回允许的方法、头等
3. 浏览器发送实际请求
2.3 简单请求 vs 预检请求
简单请求条件
- 方法:GET、POST、HEAD
- 头:Accept、Accept-Language、Content-Language、Content-Type
- Content-Type:text/plain、multipart/form-data、application/x-www-form-urlencoded
预检请求触发条件
- 方法:PUT、DELETE、PATCH
- 自定义请求头
- Content-Type:application/json
三、代码示例
3.1 CSRF 防护配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.*;
@Configuration
@EnableWebSecurity
public class CsrfConfig {
// 启用 CSRF 防护(默认启用)
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
// 使用 Cookie 存储 CSRF Token
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
// 禁用 CSRF(仅用于无状态 API)
@Bean
public SecurityFilterChain disableCsrf(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable());
return http.build();
}
// 自定义 CSRF 配置
@Bean
public SecurityFilterChain customCsrf(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(csrfTokenRepository())
.ignoringRequestMatchers("/api/public/**")
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
);
return http.build();
}
@Bean
public CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository repository = new CookieCsrfTokenRepository();
repository.setCookieName("XSRF-TOKEN");
repository.setHeaderName("X-XSRF-TOKEN");
repository.setParameterName("_csrf");
return repository;
}
}
3.2 前端 CSRF Token 处理
<!-- Thymeleaf 表单 -->
<form th:action="@{/transfer}" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
<!-- 其他表单字段 -->
<button type="submit">提交</button>
</form>
// JavaScript 获取 CSRF Token
function getCsrfToken() {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
return decodeURIComponent(cookieValue);
}
// 发送请求时携带 Token
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': getCsrfToken()
},
body: JSON.stringify(data)
});
// Axios 配置
axios.defaults.headers.common['X-XSRF-TOKEN'] = getCsrfToken();
// 或使用拦截器
axios.interceptors.request.use(config => {
config.headers['X-XSRF-TOKEN'] = getCsrfToken();
return config;
});
3.3 CORS 配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.*;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
// 方式1:全局 CORS 配置
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许的源
configuration.setAllowedOriginPatterns(Arrays.asList(
"http://localhost:*",
"https://*.example.com"
));
// 允许的方法
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS"
));
// 允许的头
configuration.setAllowedHeaders(Arrays.asList("*"));
// 允许携带凭证
configuration.setAllowCredentials(true);
// 预检请求缓存时间
configuration.setMaxAge(3600L);
// 暴露的响应头
configuration.setExposedHeaders(Arrays.asList(
"Authorization", "X-Total-Count"
));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
// 方式2:使用 CorsFilter
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
3.4 Spring Security 集成 CORS
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityCorsConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 启用 CORS
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// 其他配置
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
3.5 控制器级别 CORS
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
@CrossOrigin(
origins = "http://localhost:3000",
methods = {RequestMethod.GET, RequestMethod.POST},
allowedHeaders = "*",
allowCredentials = "true",
maxAge = 3600
)
public class ApiController {
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
// 方法级别覆盖
@PostMapping("/users")
@CrossOrigin(origins = "http://localhost:3000")
public User createUser(@RequestBody UserDTO dto) {
return userService.create(dto);
}
}
3.6 自定义 CORS 处理
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
@Component
public class CustomCorsFilter implements Filter {
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String origin = httpRequest.getHeader("Origin");
// 设置 CORS 头
httpResponse.setHeader("Access-Control-Allow-Origin",
getAllowedOrigin(origin));
httpResponse.setHeader("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS");
httpResponse.setHeader("Access-Control-Allow-Headers",
"Authorization, Content-Type");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
// 处理预检请求
if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
httpResponse.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(request, response);
}
private String getAllowedOrigin(String origin) {
// 动态验证源
List<String> allowedOrigins = Arrays.asList(
"http://localhost:3000",
"https://example.com"
);
return allowedOrigins.contains(origin) ? origin : "";
}
}
3.7 CSRF 异常处理
import org.springframework.security.web.csrf.*;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Component
public class CustomCsrfTokenRepository implements CsrfTokenRepository {
private final CookieCsrfTokenRepository delegate =
CookieCsrfTokenRepository.withHttpOnlyFalse();
@Override
public CsrfToken generateToken(HttpServletRequest request) {
return delegate.generateToken(request);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
delegate.saveToken(token, request, response);
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
CsrfToken token = delegate.loadToken(request);
// 自定义加载逻辑
if (token == null) {
// 生成新 Token
token = generateToken(request);
saveToken(token, request, null);
}
return token;
}
}
3.8 REST API CSRF 处理
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.*;
@Configuration
public class RestApiCsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// REST API 通常禁用 CSRF(使用 Token 认证)
.csrf(csrf -> csrf.disable())
// 或使用无状态的 CSRF Token
// .csrf(csrf -> csrf
// .csrfTokenRepository(new StatelessCsrfTokenRepository())
// )
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
);
return http.build();
}
}
// 无状态 CSRF Token 仓库
class StatelessCsrfTokenRepository implements CsrfTokenRepository {
@Override
public CsrfToken generateToken(HttpServletRequest request) {
String token = UUID.randomUUID().toString();
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
@Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
// 无状态,不保存
}
@Override
public CsrfToken loadToken(HttpServletRequest request) {
String token = request.getHeader("X-CSRF-TOKEN");
if (token != null) {
return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token);
}
return null;
}
}
四、实战应用场景
4.1 前后端分离 CORS 配置
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.*;
@Configuration
public class SpaCorsConfig {
@Value("${app.frontend.url}")
private String frontendUrl;
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 前端地址
configuration.setAllowedOrigins(Arrays.asList(frontendUrl));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
}
4.2 多环境 CORS 配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.*;
import java.util.*;
@Configuration
public class EnvironmentCorsConfig {
@Value("${spring.profiles.active:dev}")
private String activeProfile;
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 根据环境配置不同的 CORS 策略
switch (activeProfile) {
case "prod":
// 生产环境:严格的 CORS 策略
configuration.setAllowedOrigins(Arrays.asList(
"https://www.example.com"
));
break;
case "staging":
// 测试环境
configuration.setAllowedOriginPatterns(Arrays.asList(
"https://*.staging.example.com"
));
break;
default:
// 开发环境:宽松的 CORS 策略
configuration.setAllowedOriginPatterns(Arrays.asList("*"));
break;
}
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
五、总结与最佳实践
CSRF 防护策略
| 场景 | 推荐 |
|---|---|
| 传统 Web 应用 | 启用 CSRF |
| REST API | 禁用 CSRF(使用 Token 认证) |
| 单页应用 | 使用 Cookie CSRF Token |
CORS 配置原则
| 场景 | 推荐 |
|---|---|
| 开发环境 | 允许所有源 |
| 生产环境 | 限制具体域名 |
| 公开 API | 不允许凭证 |
最佳实践
-
CSRF:
- 敏感操作必须防护
- Token 绑定会话
- 关键操作二次验证
-
CORS:
- 最小权限原则
- 白名单机制
- 不要使用
*允许凭证
CSRF 和 CORS 是 Web 安全的基础,理解其原理并正确配置,能够有效防止跨站攻击和跨域安全问题。