什么是 CORS 跨域请求?
嗨,你好啊,我是猿java
在做 Web 开发时,CORS 跨域是我们经常遇到的问题,这篇文章,我们将一起分析什么是 CORS?CORS 的原理是什么?为什么需要 CORS?
什么是 CORS?
CORS,全称为“跨域资源共享”(Cross-Origin Resource Sharing),是一种机制,它使用额外的 HTTP 头来告诉浏览器允许一个网页从另一个域(不同于该网页所在的域)请求资源。这样可以在服务器和客户端之间进行安全的跨域通信。
当一个网页向不同源发出请求时,CORS 会通过以下几个步骤来处理:
- 预检请求(Preflight Request):对于某些类型的请求(如使用 HTTP 方法 PUT、DELETE,或者请求带有非简单头部),浏览器会首先发送一个 OPTIONS 请求,这个请求称为“预检请求”。服务器收到这个请求后,会返回一个响应头部,指明实际请求是否被允许。
- 实际请求(Actual Request):如果预检请求通过,浏览器会继续发送实际的请求。
- 响应头部(Response Headers):服务器在响应中会包含一些特定的 CORS 头部,如 Access-Control-Allow-Origin,以指示哪些域名可以访问资源。
什么是 Origin?
Origin,翻译为 源(域),在 CORS 上下文中 Origin 由三个元素组成:
1 | Origin = 协议 + 域名 + 端口 |
协议:例如 http:// 或 https://
域名:例如 www.yuanjava.com
端口:例如 80(默认 HTTP 端口)、443(默认 HTTPs 端口)
只有上述三个元素都匹配时,我们才会认为两个 URL 具有相同的来源,否则,有任何一个不相同都认为不同源。
同源策略
同源策略(Same-Origin Policy, SOP)是浏览器的一种安全机制,用于防止恶意网站通过脚本对其他网站的内容进行访问。
所谓“同源”,是指协议、域名和端口都相同。比如,以下 URL 属于同源地址:
- https://yuanjava.com/categories 和 https://yuanjava.com/archives
- https://yuanjava.com:443 和 https://yuanjava.com:443/interview
跨域请求
跨域请求是指从一个域向另一个域发起的HTTP请求。
在现代 Web 应用中,跨域请求非常常见,比如,从前端应用向不同的后端 API 服务器请求数据,或从一个 Web 服务请求另一个 Web 服务的资源,因为,同源策略默认会阻止这些请求,所以需要 CORS 机制来显式允许跨域访问。
以下 URL 则被认为是跨域请求:
- http://yuanjava.com 和 https://yuanjava.com(协议不同)
- http://yuanjava.com 和 http://blog.yuanjava.com(域名不同)
- http://yuanjava.com:80 和 http://yuanjava.com:8080(端口不同)
下图显示了 CORS 流的主要参与者:
下图展示了浏览器默认允许同源请求,而跨域请求则被阻止:
CORS 工作流程
CORS 通过在 HTTP(s) 请求和响应中使用特定的头部字段来实现跨域资源共享,具体来说,CORS 分为两种类型的请求处理方式:简单请求和预检请求。
- 简单请求:对于某些简单的 HTTP 请求(如GET、POST请求且不包含自定义头部),浏览器会直接发送请求,并在响应中检查 CORS 头部。
- 预检请求:对于复杂请求(如使用PUT、DELETE方法,或包含自定义头部),浏览器会首先发送一个OPTIONS请求,称为预检请求(Preflight Request),以确定服务器是否允许实际请求。
简单请求
简单请求是指满足以下条件的 HTTP 请求:
- 使用GET、POST、HEAD方法
- 请求头部仅包含以下字段:Accept、Accept-Language、Content-Language、Content-Type(且值为application/x-www-form-urlencoded、multipart/form-data或text/plain)
对于简单请求,浏览器会直接发送请求并在响应中检查以下 CORS 头部:
- Access-Control-Allow-Origin:指示允许访问资源的源。
- Access-Control-Allow-Credentials:指示是否允许发送凭据(如Cookies)。
- Access-Control-Expose-Headers:指示哪些头部可以作为响应的一部分被访问。
比如,下面一个示例:
客户端请求:
1 | GET /api/data |
服务器响应:
1 | 200 OK |
预检请求
对于复杂请求,浏览器会首先发送一个 OPTIONS
请求,包含以下头部字段:
- Origin:指示请求的源。
- Access-Control-Request-Method:指示实际请求将使用的方法。
- Access-Control-Request-Headers:指示实际请求将包含的自定义头部。
服务器收到预检请求后,会返回一个响应,包含以下头部字段以指示是否允许请求:
- Access-Control-Allow-Origin:表明允许访问资源的源,可以是具体的源或通配符 *;
- Access-Control-Allow-Methods:表明允许的方法,如 GET, POST, PUT, DELETE;
- Access-Control-Allow-Headers:表明允许的自定义头部;
- Access-Control-Allow-Credentials:表明是否允许发送凭据(如 Cookies);
- Access-Control-Expose-Headers:表明哪些头部可以作为响应的一部分被访问;
- Access-Control-Max-Age:表明预检请求的结果可以被缓存的时间,单位是秒;
如果预检请求通过,浏览器会继续发送实际请求。
比如,下面一个示例:
预检请求:
1 | OPTIONS /api/data |
预检响应:
1 | 204 No Content |
实际请求
1 | PUT /api/data |
实际响应:
1 | 200 OK |
如何实现 CORS?
客户端处理
客户端可以向远程服务器发送签名请求。
如下示例代码:在 CORS 请求中以 Authorization 标头的形式发送凭据:
1 | function sendAuthRequestToCrossOrigin() { |
服务器端处理
方法1:直接采用 SpringBoot 的注解 @CrossOrigin
如下示例代码如下,可以把 @CrossOrigin
加在每个 Controller 上,也可以加在它们的公共父类上:
1 | @CrossOrigin |
方法2: 采用过滤器(filter)的方式
如下示例代码:增加一个 CORSFilter 类,并实现 Filter 接口即可。
1 |
|
方法3: 配置 Configuration
如下示例代码:增加一个配置类继承 WebMvcConfigurerAdapter 或者实现 WebMvcConfigurer 接口,项目启动时,会自动读取配置。
1 | import org.springframework.context.annotation.Configuration; |
另外,在服务器,可以通过设置响应头部来细粒度配置 CORS,具体的如下:
1.允许所有源访问
1 | 200 OK |
2.允许特定源访问
1 | 200 OK |
3.允许凭据请求访问
1 | 200 OK |
4.允许特定方法和头部
1 | 200 OK |
5.设置预检请求的缓存时间
1 | 200 OK |
通常来说,在服务器解决 CORS是一种比较常见和彻底的方式,我们可以在服务器灵活的设置允许跨域访问的域名或者地址。
常见问题及解决方案
问题1: No ‘Access-Control-Allow-Origin’ header is present on the requested resource
问题描述:当浏览器发起跨域请求时,未在响应中找到 Access-Control-Allow-Origin 头部。
解决方案:确保服务器端正确设置了 Access-Control-Allow-Origin 头部。例如:
1 | 200 OK |
问题2:The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’
问题描述:当请求包含凭据时,Access-Control-Allow-Origin 头部不能设置为通配符 *。
解决方案:明确指定允许的源,并确保设置了 Access-Control-Allow-Credentials 头部。例如:
1 | 200 OK |
问题3:CORS preflight channel did not succeed
问题描述:预检请求失败,可能是由于服务器未正确处理 OPTIONS 请求。
解决方案:确保服务器正确处理 OPTIONS 请求并返回相应的 CORS 头部。例如,在 Node.js/Express 中:
1 | app.options('/api/data', (req, res) => { |
总结
CORS 是现代 Web 开发中不可或缺的机制,它允许 Web 应用在安全的前提下进行跨域资源请求,通过理解 CORS 的工作原理和配置方法,可以帮助我们有效地解决跨域请求的问题。
学习交流
如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。