@CrossOrigin 注解
@CrossOrigin 是 Spring Web 里用于开启 CORS 的注解。简单说,它决定了"哪些前端网页可以跨域访问这个后端接口"。
先理解什么是跨域
浏览器为了安全,会限制网页随便请求别的网站接口。
只要下面三者有一个不同,就算跨域:
- 协议不同:
http和https - 域名不同:
localhost和example.com - 端口不同:
localhost:3000和localhost:8080
例如:
text
前端页面:http://localhost:3000
后端接口:http://localhost:8080/api/users
虽然都在本机,但端口不同,所以浏览器认为这是跨域请求。
如果后端没有允许,浏览器会拦截请求,并在控制台看到类似 CORS 的错误。
@CrossOrigin 做了什么
@CrossOrigin 会让 Spring 在接口响应里加上一些 CORS 响应头,例如:
http
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET,POST
Access-Control-Allow-Headers: Content-Type
浏览器看到这些响应头后,才会允许前端代码读取接口返回结果。
可以标在哪里
这个注解可以标在类上,也可以标在方法上。
标在类上,表示这个 Controller 里的接口都适用:
java
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserController {
@GetMapping
public List<String> listUsers() {
return List.of("Alice", "Bob");
}
}
标在方法上,只对某个接口生效:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
@CrossOrigin(origins = "http://localhost:3000")
public List<String> listUsers() {
return List.of("Alice", "Bob");
}
}
常用属性解释
value 和 origins
value 是 origins 的别名,两者作用一样,用来配置允许访问的来源。
下面两种写法等价:
java
@CrossOrigin("http://localhost:3000")
java
@CrossOrigin(origins = "http://localhost:3000")
如果允许多个来源:
java
@CrossOrigin(origins = {
"http://localhost:3000",
"https://admin.example.com"
})
通俗理解:origins 就是"白名单",只有白名单里的前端页面可以跨域访问。
originPatterns
originPatterns 是更灵活的来源匹配方式,支持模式匹配。
例如允许某个域名下的子域名:
java
@CrossOrigin(originPatterns = "https://*.example.com")
它可以匹配:
text
https://admin.example.com
https://app.example.com
当你需要配置带通配符的来源时,通常用 originPatterns 比 origins 更合适。
allowedHeaders
allowedHeaders 表示允许前端请求携带哪些请求头。
例如:
java
@CrossOrigin(
origins = "http://localhost:3000",
allowedHeaders = {"Content-Type", "Authorization"}
)
这表示前端可以带上:
http
Content-Type: application/json
Authorization: Bearer token
默认情况下,Spring 通常会允许请求中实际申请的请求头。
exposedHeaders
exposedHeaders 表示后端允许浏览器中的前端代码读取哪些响应头。
注意:后端返回了某个响应头,不代表前端 JavaScript 一定能读到它。
例如后端返回了分页总数:
http
X-Total-Count: 120
如果前端要通过 response.headers.get("X-Total-Count") 读取,就需要暴露它:
java
@CrossOrigin(
origins = "http://localhost:3000",
exposedHeaders = "X-Total-Count"
)
methods
methods 表示允许哪些 HTTP 方法跨域访问。
例如:
java
@CrossOrigin(
origins = "http://localhost:3000",
methods = {RequestMethod.GET, RequestMethod.POST}
)
这表示跨域时只允许 GET 和 POST。
默认情况下,Spring 会根据接口本身的映射方法来决定。例如 @GetMapping 对应 GET。
allowCredentials
allowCredentials 表示是否允许浏览器在跨域请求中携带凭证。
凭证包括:
- Cookie
- HTTP 认证信息
- TLS 客户端证书
例如:
java
@CrossOrigin(
origins = "http://localhost:3000",
allowCredentials = "true"
)
前端也必须配合设置:
javascript
fetch("http://localhost:8080/api/users", {
credentials: "include"
});
重要提醒:如果允许携带 Cookie,就不要随便放开来源。因为这代表被允许的前端站点可以带着用户身份访问你的接口。
allowPrivateNetwork
allowPrivateNetwork 用于支持 Private Network Access,也就是浏览器从公网或较低安全级别的网络访问内网资源时的一类安全控制。
普通业务项目里不常用。只有当前端页面可能访问内网地址、局域网设备或私有网络服务时,才需要关注它。
例如:
java
@CrossOrigin(
origins = "https://app.example.com",
allowPrivateNetwork = "true"
)
maxAge
maxAge 表示预检请求结果可以缓存多久,单位是秒。
浏览器在发送某些跨域请求前,会先发一个 OPTIONS 请求,问后端:"我能不能这样请求?"
这个 OPTIONS 请求叫预检请求。
例如:
java
@CrossOrigin(
origins = "http://localhost:3000",
maxAge = 3600
)
表示预检结果缓存 3600 秒,也就是 1 小时。
缓存时间越长,浏览器越不需要频繁发预检请求,可以减少一次额外网络交互。
一个完整例子
java
@RestController
@RequestMapping("/api/orders")
@CrossOrigin(
origins = "http://localhost:3000",
allowedHeaders = {"Content-Type", "Authorization"},
exposedHeaders = "X-Total-Count",
methods = {RequestMethod.GET, RequestMethod.POST},
allowCredentials = "true",
maxAge = 3600
)
public class OrderController {
@GetMapping
public List<String> listOrders() {
return List.of("order-001", "order-002");
}
@PostMapping
public String createOrder(@RequestBody String body) {
return "created";
}
}
这个配置表示:
- 只允许
http://localhost:3000跨域访问 - 允许请求带
Content-Type和Authorization - 允许前端读取响应头
X-Total-Count - 允许跨域调用
GET和POST - 允许跨域携带 Cookie 等凭证
- 预检请求结果缓存 1 小时
类上和方法上同时配置会怎样
Spring 会把全局配置和局部配置合并。
一般来说:
- 像
origins、allowedHeaders、methods这种可以有多个值的配置,会倾向于合并 - 像
allowCredentials、maxAge这种只能有一个值的配置,方法上的局部配置会覆盖类上或全局配置
例如:
java
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:3000")
public class ApiController {
@GetMapping("/public")
public String publicApi() {
return "public";
}
@GetMapping("/admin")
@CrossOrigin(origins = "https://admin.example.com")
public String adminApi() {
return "admin";
}
}
/api/public 使用类上的跨域配置。
/api/admin 会结合类上和方法上的配置,具体合并规则由 Spring 的 CorsConfiguration#combine 决定。
常见错误
1. 前端配置了凭证,但后端没开
前端:
javascript
fetch("/api/users", {
credentials: "include"
});
后端却没有:
java
allowCredentials = "true"
结果浏览器仍然会拦截。
2. allowCredentials = "true" 时来源配置太宽
不要这样写:
java
@CrossOrigin(origins = "*", allowCredentials = "true")
携带 Cookie 时,应该明确指定可信来源:
java
@CrossOrigin(origins = "https://app.example.com", allowCredentials = "true")
3. 以为 CORS 是服务端权限控制
CORS 主要是浏览器安全机制。
它不能替代登录、鉴权、权限校验。
即使你配置了 CORS,接口仍然需要正常做认证和授权。
推荐用法
开发环境可以写得宽松一点:
java
@CrossOrigin(origins = "http://localhost:3000")
生产环境建议明确配置可信域名:
java
@CrossOrigin(
origins = "https://app.example.com",
allowCredentials = "true"
)
如果很多接口都需要统一配置跨域,更推荐使用全局 CORS 配置,而不是在每个 Controller 上重复写 @CrossOrigin。
如何配置全局 CORS
如果项目里很多接口都需要同一套跨域规则,不建议在每个 Controller 或每个方法上都写 @CrossOrigin。更常见的做法是写一个全局配置类。
Spring Web MVC 项目
如果你的项目使用的是 Spring MVC,也就是常见的 spring-boot-starter-web,可以这样配置:
java
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 WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("Content-Type", "Authorization")
.exposedHeaders("X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
这段配置表示:
- 只对
/api/**下的接口开启跨域 - 允许
http://localhost:3000和https://app.example.com访问 - 允许常见的增删改查请求方法
- 允许请求携带
Content-Type和Authorization - 允许前端读取响应头
X-Total-Count - 允许跨域携带 Cookie
- 预检请求缓存 1 小时
如果你想让所有接口都支持跨域,可以把:
java
registry.addMapping("/api/**")
改成:
java
registry.addMapping("/**")
不过生产环境更建议只给需要跨域的接口开启,例如 /api/**。
使用通配域名
如果需要允许一批子域名,可以使用 allowedOriginPatterns:
java
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 WebCorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOriginPatterns("https://*.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
例如它可以允许:
text
https://admin.example.com
https://app.example.com
Spring WebFlux 项目
如果你的项目使用的是 Spring WebFlux,也就是 spring-boot-starter-webflux,可以这样配置:
java
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 WebFluxCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedOrigin("https://app.example.com");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.addExposedHeader("X-Total-Count");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return new CorsWebFilter(source);
}
}
全局配置和 @CrossOrigin 怎么选
一般可以这样判断:
- 少数几个接口需要跨域:用
@CrossOrigin - 大量接口规则相同:用全局 CORS 配置
- 不同模块有不同规则:全局配置为主,个别接口再用
@CrossOrigin微调
实际项目里,更推荐把跨域规则集中管理。这样以后前端域名变化、请求头变化、是否允许 Cookie 变化时,只需要改一个地方。
一句话总结
@CrossOrigin 就是 Spring 给接口设置"跨域访问白名单"的注解。它告诉浏览器:哪些前端地址可以访问这个接口、可以用哪些方法、能带哪些请求头、能不能带 Cookie、预检结果能缓存多久。