@CrossOrigin 注解

@CrossOrigin 注解

@CrossOrigin 是 Spring Web 里用于开启 CORS 的注解。简单说,它决定了"哪些前端网页可以跨域访问这个后端接口"。

先理解什么是跨域

浏览器为了安全,会限制网页随便请求别的网站接口。

只要下面三者有一个不同,就算跨域:

  • 协议不同:httphttps
  • 域名不同:localhostexample.com
  • 端口不同:localhost:3000localhost: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");
    }
}

常用属性解释

valueorigins

valueorigins 的别名,两者作用一样,用来配置允许访问的来源。

下面两种写法等价:

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

当你需要配置带通配符的来源时,通常用 originPatternsorigins 更合适。

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}
)

这表示跨域时只允许 GETPOST

默认情况下,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-TypeAuthorization
  • 允许前端读取响应头 X-Total-Count
  • 允许跨域调用 GETPOST
  • 允许跨域携带 Cookie 等凭证
  • 预检请求结果缓存 1 小时

类上和方法上同时配置会怎样

Spring 会把全局配置和局部配置合并。

一般来说:

  • originsallowedHeadersmethods 这种可以有多个值的配置,会倾向于合并
  • allowCredentialsmaxAge 这种只能有一个值的配置,方法上的局部配置会覆盖类上或全局配置

例如:

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:3000https://app.example.com 访问
  • 允许常见的增删改查请求方法
  • 允许请求携带 Content-TypeAuthorization
  • 允许前端读取响应头 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、预检结果能缓存多久。

相关推荐
土狗TuGou1 小时前
SQL内功笔记 · 第7篇:CTE&临时表&递归
数据库·笔记·后端·sql·mysql
XiYang-DING1 小时前
【Spring】日志
java·数据库·spring
Reisentyan1 小时前
[Pro]GoLang Learn Data Day 5
开发语言·后端·golang
雪度娃娃1 小时前
转向现代C++——优先选用删除函数而非private未定义函数
java·jvm·c++
Kurisu5751 小时前
深度拆解:从 Linux 内核 Namespace 与 Cgroups 洞察容器技术的底层本质
java·linux·运维
罗超驿1 小时前
11.LeetCode 1004. 最大连续1的个数 III | 滑动窗口解法详解(Java)
java·算法·leetcode
努力发光的程序员1 小时前
面试官与程序员谢飞机的3轮Java大厂面试问答实录:涵盖Spring Boot、微服务与数据库技术
java·jvm·spring boot·redis·面试·hibernate·microservices
橙淮1 小时前
并发编程(四)
java·jvm
z落落1 小时前
C# Stack栈 / Queue队列+所有集合 终极一页汇总(全覆盖、零遗漏)
java·开发语言·c#