因为浏览器的同源安全策略,我们在访问一个网站的时候,只能请求同一个域名,同一种协议下的JS文件。 上图如果我们开启了CORS检查,在打开 domain-a.com
页面时请求 domain-a.com
域名的所有资源都很正常,但是请求 domain-b.com
的东西就会被拒绝。 所以当我们需要跨域请求时就很麻烦了。以前我们使用 JSONP 来解决跨域问题,大致是在 DOM 中加入一个 <script>
Tag,由它去请求跨域资源,请求结束后会调用 callback
参数指定的函数,类似这样:
let s = document.createElement("script");
s.src = "jsonp_demo_db.php?callback=myDisplayFunction";
document.body.appendChild(s);
// 函数myDisplayFunction会被调用
后来(2009年)有了Cross-Origin Resource Sharing(CORS, 跨域资源共享)之后就不再需要 JSONP 了。 CORS的工作原理很简单,被请求的服务器在Response Header里告诉客户端(这里是浏览器)是否允许被跨域请求,以及允许的域名是哪些,如果不符合条件就拒绝: 比如上图,服务器返回 Access-Control-Allow-Origin: *
意味着允许任意域名请求该资源。这样即使从 domain-a.com
的页面请求 domain-b.com
的资源也可以正常放过。
1. Nginx 修改支持跨域
如果 domain-a.com
在自己手里那修改 http repsonse header 是小事,比如修改 nginx 配置文件:
location ~* \.(eot|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
}
2. CDN 修改支持跨域
我上一次遇到 CORS 错误是在 domain-a.com
请求托管在 domain-cdn.com
的资源的时候,修改配置稍微有点麻烦。 以腾讯云 CDN 为例,这里有两层服务。一个是对象存储(Cloud Object Storage),即文件的实际存储功能;另一个是内容分发网络(Content Delivery Network,CDN),即用户实际接入的CDN节点。 以前腾讯云的对象存储支持默认域名访问,官方也提供了跨域访问 CORS 设置。有些 Google CORS 错误的结果会告诉你到这里设置,但其实这并不是用户实际接入的网络,所以配置也没什么用(以前可以单独使用默认cos域名访问对象存储内容,现在不支持了,都需要自己接入自定义域名)。 所以要解决用户端的CORS错误,我们需要修改自定义CDN域名的配置。 在内容分发网络CDN → 域名管理 → 高级配置 → HTTP响应头配置,这个地方新增规则,可以修改CDN服务器的HTTP response。
3. 有哪些Response Headers可以设置?
- Access-Control-Allow-Origin 允许跨域访问的域名, * 表示任意域名。
- Access-Control-Allow-Methods 允许的HTTP Method,比如 GET/POST。
- Access-Control-Allow-Credentials 如果客户端发上来的Request credential设置为
include
,则只有当我们把Access-Control-Allow-Credentials
设置为true
时,浏览器才会把response暴露给前端JS。 - Access-Control-Expose-Headers 告诉浏览器哪些response headers可以暴露给前端JS。
- Access-Control-Max-Age 一个CORS preflight request的有效期。浏览器会自动发起 CORS 预检请求(preflight requests)给服务器,检查是否支持跨域访问。比如在客户端发起一个真正的
DELETE
请求之前,先preflight一下,如果服务器说允许,浏览器再发起真正的请求。 - Access-Control-Allow-Methods 允许的跨越请求Methods,*表示都允许。比如:
Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Methods: *
- Access-Control-Allow-Headers 可以支持自定义response header。有几个安全的响应头无需列出,其他的可以自行设置,比如:
Access-Control-Allow-Headers: X-Custom-Header, Upgrade-Insecure-Requests
4. 为什么 tag不设置type="module"就不会有CORS报错?
tag 加了 type="module"
就意味着这是一个module script,相对的,不带这个属性则为classic script。我们可以写个简单的 Demo 测试一下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<a href="#" id="test-link">Test CORS</a>
<script type="module" crossorigin>
function test() {
var url = 'https://some-domain.com/some.js';
var xhr = new XMLHttpRequest();
xhr.open('HEAD', url);
xhr.onload = function () {
var headers = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n');
alert('request success, CORS allow.\n' +
'url: ' + url + '\n' +
'status: ' + xhr.status + '\n' +
'headers:\n' + headers);
};
xhr.onerror = function () {
alert('request error, maybe CORS error.');
};
xhr.send();
}
const link = document.getElementById("test-link")
link.addEventListener('click', function() {
test()
})
</script>
</body>
</html>
如果 'https://some-domain.com/some.js' 不支持CORS response headers,浏览器就会报错。 原因是浏览器支持 module scirpts 时规定了必须使用 CORS 协议作为跨域资源请求,可以参考这里。
Unlike classic scripts, module scripts require the use of the CORS protocol for cross-origin fetching.
所以同样的代码,如果不使用 <script type="module"></script>
,就不会触发 CORS 报错。