跨域共享(CORS)&CrossOrigin注解

1. 什么是同源策略 (Same-Origin Policy, SOP)?

要理解跨域,首先必须理解同源策略。这是浏览器最核心、最基本的安全功能。
“源”(Origin) 由三个部分组成:

  1. 协议 (Protocol):例如 httphttps
  2. 域名 (Domain):例如 www.example.comapi.example.com
  3. 端口 (Port):例如 804438080
    只有当这三者完全相同时,两个 URL才属于“同源”。
URL 1URL 2是否同源原因
http://a.com:80/http://a.com:80/path/协议、域名、端口都相同
http://a.com:80/https://a.com:80/协议不同 (http vs https)
http://a.com:80/http://b.com:80/域名不同 (a.com vs b.com)
http://a.com:80/http://a.com:8080/端口不同 (80 vs 8080)

同源策略的目的: 它的主要目的是防止恶意网站读取其他网站的敏感数据。想象一下,如果你在浏览器的一个标签页中登录了你的网上银行,然后在另一个标签页中打开了一个恶意网站。如果没有同源策略,那个恶意网站的 JavaScript 脚本就可以向你的网上银行发送请求,并读取返回的 JSON 数据(比如你的账户余额、交易记录等),然后将这些数据发送到它自己的服务器。同源策略阻止了这种行为的发生。
同源策略的限制: 浏览器会限制JS脚本发起的跨源 HTTP 请求。具体来说,一个源的脚本不能读取另一个源的响应数据。

浏览器并不会阻止请求的发送。对于某些类型的请求,请求实际上已经到达了服务器,服务器也处理了并返回了响应。但是,浏览器在接收到响应后,会检查其来源,如果发现是跨源的,并且没有得到服务器的明确许可,就会拦截这个响应,不允许你的 js代码访问它,并在控制台抛出跨域错误。

2. 为什么需要跨域?

在现代 Web 开发中,前后端分离架构(如 Vue/React/Angular + Spring Boot)和微服务架构非常普遍。

  • 前后端分离:前端应用(例如运行在 http://localhost:8080)需要调用后端 API(例如运行在 http://localhost:9090/api)。这显然是跨域的(端口不同)。
  • 微服务:一个主应用(https://app.com)可能需要调用不同子域下的服务,比如用户服务(https://user-api.com)和订单服务(https://order-api.com)。这也是跨域的。
    因此,我们需要一种安全、可控的方式来“绕过”同源策略的限制。

3. CORS (Cross-Origin Resource Sharing) 跨域资源共享

CORS 是一种 W3C 标准,它允许服务器明确声明哪些源(Origin)有权限访问其资源。它不是去“关闭”同源策略,而是为同源策略增加了一套“白名单”机制。
CORS 的工作原理: CORS 通过一系列HTTP 头部字段来工作。浏览器和服务器通过这些头部字段进行“协商”,以确定请求是否安全。

A. 简单请求 (Simple Requests)

如果一个请求同时满足以下所有条件,它就是一个“简单请求”:

  1. 请求方法是以下三者之一:
    • GET
    • HEAD
    • POST
  2. HTTP 头部不超出以下几种字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(值仅限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  3. 请求中没有自定义的 HTTP 头部(例如 Authorization)。
    简单请求的流程
  4. 浏览器在请求头中自动添加一个 Origin 字段,表明请求来自哪个源。
GET /data HTTP/1.1 
Host: api.example.com 
Origin: http://ui.another.com
  1. 服务器收到请求后,检查 Origin 字段的值。
  2. 如果服务器允许这个源的请求,它会在响应头中添加一个 Access-Control-Allow-Origin 字段,并将其值设为请求的 Origin 值(或者 * 表示允许所有源)。
HTTP/1.1 200 OK 
Access-Control-Allow-Origin: http://ui.another.com 
Content-Type: application/json {"data": "some value"}
  1. 浏览器收到响应后,检查 Access-Control-Allow-Origin 头部。如果值匹配或者为 *,就将响应数据交给 JavaScript 处理。否则,浏览器会拦截响应,并在控制台报错。
B. 非简单请求 (Preflighted Requests)

不满足“简单请求”条件的都是“非简单请求”。常见的例子包括:

  • 使用了 PUT, DELETE, PATCH 等方法的请求。
  • Content-Typeapplication/jsonPOST 请求。
  • 带有自定义请求头(如 Authorization: Bearer ...)的请求。
    非简单请求的流程(包含“预检”请求): 在发送实际请求之前,浏览器会自动发送一个“预检”(Preflight)请求。这是一个 OPTIONS 方法的请求。
  1. 预检请求 (OPTIONS Request): 浏览器向服务器发送一个 OPTIONS 请求,其中包含了几个关键的头部:
    • Origin: 表明请求来源。
    • Access-Control-Request-Method: 告知服务器,实际请求将使用哪种 HTTP 方法。
    • Access-Control-Request-Headers: 告知服务器,实际请求将携带哪些自定义头部。
OPTIONS /data/123 HTTP/1.1
Host: api.example.com
Origin: http://ui.another.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization
  1. 预检响应 (OPTIONS Response): 服务器收到预检请求后,进行判断,如果允许,就在响应中返回一系列 Access-Control-* 头部,告诉浏览器它允许的范围。这个响应没有 body
    • Access-Control-Allow-Origin: 允许的源。
    • Access-Control-Allow-Methods: 允许的方法列表(如 GET, POST, PUT, DELETE)。
    • Access-Control-Allow-Headers: 允许的自定义头部列表。
    • Access-Control-Max-Age: 预检请求的有效时间(秒)。在此期间,浏览器无需为相同的请求再次发送预检请求。
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://ui.another.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600 
  1. 浏览器决策: 浏览器检查预检响应。如果服务器允许了实际请求的方法和头部,浏览器才会发送实际的请求(例如 PUT 请求)。否则,就在控制台报错,实际请求根本不会被发送。
  2. 实际请求与响应: 如果预检通过,浏览器发送实际请求(和简单请求一样,也带着 Origin 头)。服务器处理后返回数据,并且响应中必须再次包含 Access-Control-Allow-Origin
4. Spring 中的 @CrossOrigin 注解

@CrossOrigin 是 Spring Framework 提供的一个注解,用于在服务器端方便地配置 CORS。你可以把它看作是自动帮你处理上面提到的那些 Access-Control-* 响应头的工具。

  1. 使用位置
  • @CrossOrigin 可以用在两个级别:
    • 方法级别:只对被注解的那个 Controller 方法生效。
    • 类(Controller)级别:对该 Controller 下的所有方法都生效。如果方法上也有 @CrossOrigin,则方法级别的配置会覆盖类级别的配置。
  1. 基本用法
  • 场景:允许来自 http://localhost:8080 的所有跨域请求访问这个接口。
@RestController
public class MyController {
    @CrossOrigin(origins = "http://localhost:8080") // 精确指定允许的源
    @GetMapping("/data")
    public String getData() {
        return "This is sensitive data.";
    }
}
  • 如果整个 Controller 都需要允许这个源,可以把注解放在类上:
@RestController
@CrossOrigin(origins = "http://localhost:8080") // 应用于该类所有方法
public class UserController {
	...
}
5. @CrossOrigin 的常用属性

@CrossOrigin 注解提供了丰富的属性,可以精细化地控制 CORS 策略,这些属性直接对应 CORS 的响应头

  • origins (或 value):
    • 作用:指定允许的源列表,对应 Access-Control-Allow-Origin
    • 类型String[]
    • 示例@CrossOrigin(origins = {"http://a.com", "http://b.com"})
    • 特殊值"*" 表示允许所有源。但在生产环境中要慎用,特别是当 allowCredentialstrue 时。
  • methods
    • 作用:指定允许的 HTTP 方法,对应 Access-Control-Allow-Methods
    • 类型RequestMethod[]
    • 示例@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.POST})
    • 默认值:允许所有方法。
  • allowedHeaders
    • 作用:指定允许的请求头,对应 Access-Control-Allow-Headers
    • 类型String[]
    • 示例@CrossOrigin(allowedHeaders = {"Content-Type", "Authorization"})
    • 特殊值"*" 表示允许所有请求头。
    • 默认值:允许所有请求头。
  • exposedHeaders
    • 作用:指定浏览器可以访问的响应头。默认情况下,浏览器只能访问一些简单响应头(如 Cache-Control, Content-Type 等)。如果你在响应头中设置了自定义头部(比如 JWT Token),必须在这里声明,前端才能获取到。
    • 类型String[]
    • 示例@CrossOrigin(exposedHeaders = "X-Auth-Token")
  • allowCredentials
    • 作用:布尔值,是否允许发送 Cookie,对应 Access-Control-Allow-Credentials
    • 类型String (“true” or “false”)
    • 默认值"false"
    • 重要:如果设为 trueorigins 不能*,必须是具体的域名。这是浏览器安全策略的要求。前端的 AJAX 请求(如 Axios 或 Fetch API)也需要设置相应的选项(withCredentials: true)才能发送 Cookie。
    • 示例@CrossOrigin(origins = "http://trusted.com", allowCredentials = "true")
  • maxAge
    • 作用:指定预检请求的缓存时间(秒),对应 Access-Control-Max-Age
    • 类型long
    • 默认值1800 (30分钟)。
    • 示例@CrossOrigin(maxAge = 3600)
6. 全局配置 vs @CrossOrigin

虽然 @CrossOrigin 很方便,但在整个应用中有很多 Controller 都需要相同的 CORS 策略时,为每个类都添加注解会很繁琐。更好的做法是进行全局配置
在 Spring Boot 中,可以通过实现 WebMvcConfigurer 接口来配置全局 CORS 规则。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 所有路径
                .allowedOrigins("*")  // 所有源
                .allowedMethods("GET", "POST", "PUT", "DELETE")  // 允许方法
                .allowCredentials(true)  // 允许凭证
                .maxAge(3600);  // 缓存1小时
    }
}

全局配置和 @CrossOrigin 的关系

  • 如果一个请求路径同时匹配了全局配置和 @CrossOrigin 注解,Spring 会优先采用 @CrossOrigin 的配置(更具体的配置优先)。
  • 最佳实践:使用全局配置来定义通用的、全站范围的 CORS 策略,然后只在需要特殊处理的个别 Controller 或方法上使用 @CrossOrigin 进行覆盖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值