文章首发公众号【风象南】
在Web开发中,浏览器的同源策略(Same-Origin Policy)是一项重要的安全机制,它限制了一个源(Origin)中加载的文档或脚本如何与另一个源的资源进行交互。所谓同源,指的是协议、域名和端口号都相同。当前端应用试图请求与自身不同源的后端API时,就会遇到跨域问题。
例如,当 http://frontend.com
的前端应用尝试访问 http://backend.com/api
的后端服务时,浏览器会阻止这种请求,并在控制台报错:
Access to XMLHttpRequest at 'http://backend.com/api' from origin 'http://frontend.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
跨域资源共享(CORS,Cross-Origin Resource Sharing)是一种标准机制,允许服务器声明哪些源可以访问其资源。在SpringBoot应用中,有多种方式可以解决跨域问题,下面详细介绍6种常见的解决方案。
这是最简单直接的方式,通过在Controller类或特定方法上添加@CrossOrigin
注解来允许跨域请求。
// 在方法级别允许跨域
@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(origins = "http://example.com")
@GetMapping("/users")
public List getUsers() {
// 方法实现
return userService.findAll();
}
@GetMapping("/roles")
public List getRoles() {
// 此方法不允许跨域
return roleService.findAll();
}
}
// 在类级别允许跨域
@CrossOrigin(origins = {"http://example.com", "http://localhost:3000"})
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping
public List getAllProducts() {
// 方法实现
return productService.findAll();
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
// 方法实现
return productService.findById(id);
}
}
通过实现WebMvcConfigurer
接口并重写addCorsMappings
方法,可以在全局范围内配置CORS规则。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://example.com", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600); // 1小时内不需要再预检(发OPTIONS请求)
}
}
通过定义CorsFilter
作为一个Bean,可以在过滤器级别处理跨域请求,这种方式比WebMvcConfigurer
的优先级更高。
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许的源
config.addAllowedOrigin("http://example.com");
config.addAllowedOrigin("http://localhost:3000");
// 允许的HTTP方法
config.addAllowedMethod("*");
// 允许的头信息
config.addAllowedHeader("*");
// 允许携带认证信息(Cookie等)
config.setAllowCredentials(true);
// 预检请求的有效期,单位为秒
config.setMaxAge(3600L);
// 对所有URL应用这些配置
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
如果项目使用了Spring Security,需要在Security配置中允许CORS,否则Security可能会拦截跨域请求。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(Customizer.withDefaults()) // 使用CorsConfigurationSource的默认配置
.csrf().disable()
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://example.com", "http://localhost:3000"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
在微服务架构中,可以在API网关层统一处理跨域问题,这样后端微服务就不需要各自配置CORS了。
// Spring Cloud Gateway配置
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user_service_route", r -> r.path("/api/users/**")
.uri("lb://user-service"))
.route("product_service_route", r -> r.path("/api/products/**")
.uri("lb://product-service"))
.build();
}
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
headers.add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
headers.add("Access-Control-Allow-Headers", "Authorization, Content-Type");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Max-Age", "3600");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
通过配置前端开发服务器代理 (开发环境) 或使用Nginx (生产环境) 等反向代理服务器,可以间接解决跨域问题。这种方式实际上是绕过了浏览器的同源策略,而不是直接在后端解决CORS。
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
}
}
}
}
server {
listen 80;
server_name frontend.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend.example.com:8080/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
方案 | 实现难度 | 灵活性 | 维护成本 | 适用场景 |
---|---|---|---|---|
@CrossOrigin注解 | 低 | 高 | 高 | 小型项目,特定API需要跨域 |
WebMvcConfigurer | 中 | 中 | 低 | 大多数Spring Boot应用 |
CorsFilter | 中 | 中 | 低 | 需要优先级高的CORS处理 |
Spring Security | 高 | 高 | 中 | 有安全需求的应用 |
网关层面解决 | 高 | 高 | 低 | 微服务架构 |
代理服务器 | 中 | 高 | 中 | 生产环境,严格的安全要求 |
Access-Control-Allow-Origin: *
,应该明确指定允许的源Access-Control-Max-Age
以减少预检请求跨域请求是前后端分离开发中不可避免的问题,Spring Boot提供了多种解决方案。从简单的@CrossOrigin
注解到复杂的网关配置,我们可以根据项目规模和需求选择合适的方案。在实际开发中,建议综合考虑安全性、灵活性和维护成本,选择最适合项目的CORS解决方案。
对于大多数Spring Boot应用,推荐使用全局CORS配置(WebMvcConfigurer)方案,它提供了良好的平衡性;而对于微服务架构,则推荐在网关层统一处理CORS问题,以减少后端服务的配置负担。
无论选择哪种方案,都应该遵循"最小权限原则" ,只允许必要的源访问必要的资源,确保系统的安全性。