一、什么是跨域?
跨域是指浏览器发起的请求,其目标服务器与当前页面的来源(域名、协议、端口)不一致。例如:
- 同源请求: 页面和接口来源一致,比如:
- 页面地址:
http://example.com
- 请求接口:
http://example.com/api
- 页面地址:
- 跨域请求: 页面和接口来源不同,比如:
- 页面地址:
http://localhost:3000
- 请求接口:
http://localhost:8080/api
- 页面地址:
跨域问题的根源在于 浏览器的同源策略(Same-Origin Policy),这是一种安全机制,防止恶意站点通过 JavaScript 等方式访问用户敏感数据。
同源策略限制:浏览器的同源策略只允许当前网页(前端)的域名、端口、和协议与后端一致。如果前后端运行在不同的端口、协议或域名下(如前端在 http://localhost:3000
,后端在 http://localhost:8080
),这就是跨域,浏览器会阻止请求。
跨域的目的:
- 如果跨域配置仅允许特定来源(如前端站点
http://localhost:3000
)访问,那就是为了让你的前端能调用后端。 - 如果配置允许所有来源(
*
),则是为了对任何来源的请求都开放,可能包括来自其他网站或恶意来源。
二、跨域问题的表现及解决方案
表现: 当浏览器发起跨域请求时,如果目标服务器没有正确配置跨域资源共享(CORS),会报类似以下错误:
Access to XMLHttpRequest at 'http://example.com/api' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
解决方案:
目标服务器通过 CORS 协议,明确告诉浏览器允许跨域请求。CORS 协议依赖以下 HTTP 头部字段:
Access-Control-Allow-Origin
:允许的请求来源(如http://localhost:3000
或*
)。Access-Control-Allow-Methods
:允许的请求方法(如GET, POST
)。Access-Control-Allow-Headers
:允许的请求头(如Content-Type, Authorization
)。Access-Control-Allow-Credentials
:是否允许发送 Cookies 和凭据。
三、Spring Boot 中的跨域处理
Spring Boot 提供了多种方式来配置 CORS,根据项目需求灵活选择。
1. 局部跨域配置(@CrossOrigin
)
使用 @CrossOrigin
注解,在控制器或具体方法上配置跨域规则,适合简单场景。
示例代码:
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@CrossOrigin(origins = "http://localhost:3000") // 允许来自 http://localhost:3000 的请求
@GetMapping("/api/data")
public String getData() {
return "Hello, CORS!";
}
}
优缺点:
- 优点: 配置简单,针对单个接口有效。
- 缺点: 对于多个接口的跨域需求不方便管理。
2. 全局跨域配置(针对Controller层)
方式 1:CorsRegistry(针对Spring MVC)
通过实现 WebMvcConfigurer
接口,配置全局跨域规则,适用于基于 Spring MVC 的项目。
示例代码:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 匹配所有接口
.allowedOrigins("http://localhost:3000") // 允许的请求来源
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
.allowedHeaders("*") // 允许的请求头
.allowCredentials(true) // 允许携带 Cookies 或凭据
.maxAge(3600); // OPTIONS 预检请求的缓存时间
}
}
根据CORS规范,当
allowCredentials(true)
时,allowedOrigins
必须指定具体的源,不能是通配符 "*
"
- 当
allowCredentials
设置为true
时,意味着请求可以携带认证信息(如 Cookies)- 如果同时允许所有源(
*
),会存在严重的安全漏洞- 攻击者可以从任何域发送包含凭证的跨域请求
如果不遵守这个规定,就无法允许任何的跨域请求并报错403。(博主血的教训)
方式 2:CorsWebFilter(针对Spring WebFlux)
对于基于 Spring WebFlux 的响应式项目,需要使用 CorsWebFilter
处理跨域。
示例代码:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*"); // 允许所有来源
config.addAllowedHeader("*"); // 允许所有请求头
config.addAllowedMethod("*"); // 允许所有方法
config.setAllowCredentials(true); // 允许凭据
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
两种方式的区别
-
CorsRegistry:
- 属于 Spring MVC,用于配置基于 Servlet 的传统 Web 应用。
- 实现
WebMvcConfigurer
接口,通过addCorsMappings
方法设置 CORS 规则。 - 常用于传统的 Spring Boot Web 应用。
-
CorsWebFilter:
- 属于 Spring WebFlux,用于响应式(Reactive)编程模型的项目。
- 基于过滤器机制实现,配置较灵活。
- 适合需要处理响应式流的项目(如高并发或非阻塞请求)。
3. Spring Security 跨域配置
如果不通过上面说到的针对Controller层的全局跨域配置,也可以通过在Spring Security的SecurityFilterChain中使用 http.cors()来支持跨域,与之前的Controller层的全局跨域配置二者取其一就可以。
代码示例:
@Configuration
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(List.of("*")); // 允许所有源
configuration.setAllowedMethods(List.of("*")); // 允许所有方法
configuration.setAllowedHeaders(List.of("*")); // 允许所有请求头
configuration.setAllowCredentials(true); // 注意:使用 * 时必须设置为 false
return configuration;
}));
return http.build();
}
- Spring Security中配置跨域的时候一般不用AllowedOrigin,而使用AllowedOriginPatterns,这样就可以规避之前提到的CORS规范(AllowedOriginPatterns设为"
*
"的同时AllowCredentials可以设为true) - 并且
setAllowedOriginPatterns
方法允许使用更灵活的匹配模式,可以在URL中使用通配符,从而匹配多个源。例如:"http://*.example.com"
4.当同时配置了全局跨域配置与 Spring Security 跨域配置
4.1 Spring的过滤器链顺序
Spring应用中的请求处理遵循以下的过滤器链顺序:
- Servlet容器过滤器(如
CorsFilter
)。 - Spring Security过滤器(如认证、授权过滤器)。
- Spring MVC拦截器(如
HandlerInterceptor
)。
4.2 CORS过滤器的执行顺序
-
Spring Security的CORS配置:如果配置了
HttpSecurity.cors()
,Spring Security将自动注册一个CorsFilter
,该过滤器位于Spring Security的过滤器链中。这意味着它会在Spring Security的认证和授权过滤器之后执行。 -
WebMvcConfigurer
的CORS配置:Spring MVC内部也有一个CorsFilter
,它位于Spring MVC的处理链中,通常在Spring Security过滤器链之后执行。
4.3 两种配置的交互
当同时使用 WebMvcConfigurer
和 Spring Security 的 HttpSecurity.cors()
来配置CORS时:
-
Spring Security的CORS配置优先于
WebMvcConfigurer
的CORS配置:因为Spring Security的过滤器位于过滤器链的前端,先于Spring MVC的过滤器执行。因此,Spring Security的CORS配置会首先生效。 -
潜在的配置冲突:如果两者的CORS配置不一致,可能导致预期之外的行为。通常,建议选择其中一种方式进行CORS配置,以避免混淆和冲突。
四、Spring Cloud Gateway 的跨域处理
在微服务架构中,Spring Cloud Gateway 通常作为 API 网关,代理不同微服务的请求。跨域请求可以在网关层统一处理,避免每个微服务都重复配置跨域。
方式 1:使用 CorsWebFilter
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class GatewayCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 不能使用 "*",需要指定具体域名
config.addAllowedOrigin("http://localhost:5173");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
方式 2:通过 application.yml
配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "http://localhost:3000"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
区别:
- Spring Cloud Gateway 的
CorsWebFilter
作用于网关层,处理所有通过网关的跨域请求。 - 普通 WebFlux 项目中的
CorsWebFilter
仅处理单个服务的跨域。
根据CORS规范,当
allowCredentials(true)
时,allowedOrigins
必须指定具体的源,不能是通配符 "*
"
- 当
allowCredentials
设置为true
时,意味着请求可以携带认证信息(如 Cookies)- 如果同时允许所有源(
*
),会存在严重的安全漏洞- 攻击者可以从任何域发送包含凭证的跨域请求
如果不遵守这个规定,就无法允许任何的跨域请求并报错403。(博主血的教训)