跨域资源共享-CORS
在本地电脑双击打开html文件,浏览器地址栏上的url通常是这样的:file:///D:/xxx/index.html
以 file:///文件路径 这种打开文件的方式,叫做本地文件协议 ,它是浏览器用来访问并打开位于用户计算机(本地磁盘)上的文件的协议。
通过 本地文件协议
打开的页面,其 同源策略 会将每个文件甚至每个目录都视为一个独立的、互不信任的"源"。这导致向任何 HTTP 地址发送请求都会被视为 跨源请求 ,而浏览器默认会禁止这种请求,阻止一个"源"的文档或脚本与另一个"源"的资源进行交互,除非对方明确允许。
总而言之,服务器端几乎无法为 file:
协议来源的请求正确配置 CORS。
当你尝试在 file:
协议页面中使用 fetch
或 XMLHttpRequest
发送 HTTP 请求时,浏览器控制台会抛出类似以下的错误:
- Chrome/Edge:
Access to fetch at 'http://example.com/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
- Firefox:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://example.com/. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
这个错误明确指出了请求是从源 'null'
(即 file:
协议)发起的,并且被 CORS 策略阻止了。
所以,我们需要在本地起一个服务,去做跨域的测试。
做跨域测试前,需要了解以下的内容:
预检请求
什么情况下会触发预检请求?
预检请求的成功或者失败是服务端的配置决定的吗?分别是什么配置?
预检请求的作用,是浏览器问服务端,这个请求是否允许访问,如果服务端允许,则预检请求发送成功,然后浏览器才会正式发送第二次请求,对于复杂请求来说。如果预检请求失败了,说明服务端禁止这个请求
如果前端和后端都部署在同一台服务器上,也就是说前后端的地址的域名、端口完全一样,这个时候是否会发生预检请求? 预检请求的触发条件与请求的"源" 无关,而是与请求本身的特性有关。无论是否同源,只要你的请求满足以下任一条件,浏览器就会先发送预检请求:
-
使用了非简单方法 (Non-simple Methods):
- 简单方法 :
GET
,HEAD
,POST
- 非简单方法 :
PUT
,DELETE
,CONNECT
,OPTIONS
,TRACE
,PATCH
,以及任何自定义方法(如UPDATE
)。
- 简单方法 :
-
设置了非简单首部 (Non-simple Headers):
- 简单首部 :
Accept
,Accept-Language
,Content-Language
,Content-Type
(值有限制,见下一条),DPR
,Downlink
,Save-Data
,Viewport-Width
,Width
。 - 非简单首部 :任何自定义首部(如
X-Requested-With
,Authorization
(在某些情况下)),以及Content-Type
的值不属于简单值。
- 简单首部 :
-
Content-Type
的值不是以下三种之一:
js
- `application/x-www-form-urlencoded`
- `multipart/form-data`
- `text/plain`
如果你使用 application/json
或 text/xml
,一定会触发预检。
"跨域问题"本质上是浏览器和服务器之间的权限协商问题,而不是前端代码本身的问题。 解决跨域问题的关键永远在于后端服务器的正确配置 。
在正常情况下,简单请求绝对不会触发预检请求 。而是会直接发送 这个真正的请求(如 GET
, POST
)到服务器。
测试代码
js
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:5500');
res.header('Access-Control-Allow-Headers', 'Authorization,Content-Type');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT');
next();
})
app.put('/preflight-test', (req, res) => {
res.json({
msg: 'preflight-test'
})
})
app.listen(8080, () => {
console.log('Server is running on PORT 8080.');
})
复杂请求测试
以下是用vscode插件Live Server启动源为http://127.0.0.1:5500的本地服务器打开的html测试文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
fetch('http://localhost:8080/preflight-test', {
method: 'put'
});
</script>
</body>
</html>

可以看到,有两个一模一样的请求。但一个是OPTIONS请求,一个是PUT请求。
修改请求头
js
fetch('http://localhost:8080/preflight-test', {
method: 'PUT',
headers: {
'X-Custom-Header': 'test-value'
}
});
可以发现,虽然源匹配上了,但是还是发生了cors的报错。请求头不对也会导致跨域失败问题。 即使配置了允许源(Origin),但如果请求头(Headers)不匹配,同样会导致跨域预检请求失败。
Origin
只是跨域访问的"第一道关卡"。通过了它,只意味着浏览器愿意把你的请求"派送"到服务器。而请求的"具体内容"(方法、头信息)是否被接受,还需要由服务器返回的 Allow-Methods
和 Allow-Headers
来决定。
CORS失败 ≠ 连接失败 。它特指浏览器在收到了服务器响应 后,根据响应头中的CORS规则进行校验,发现权限不足,从而主动阻止前端JavaScript代码访问响应结果。
因此,Origin
、Methods
、Headers
三者是且(AND) 的关系,必须全部满足,请求才能成功。