CORS学习分享
http://localhost:93/' from origin 'http://localhost:91' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
在前端用Ajax(XMLHttpRequest或支持fetch)请求服务端API的时候,应该都有碰到过这个这个错误,那为什么会会有这个错误呢?
其实是浏览器因为安全性的考量,有一个东西叫做同源策略
http://example.com/app1/index.html
http://example.com/app1/index.html
同源
协议、主机和端口号都一样
2
http://Example.com:80
http://example.com
同源
服务器默认通过端口 80 传递 HTTP 内容
3
http://example.com/app1
https://example.com/app2
不同源
协议不一样
4
http://example.com
http://www.example.com
不同源
主机不一样
5
http://example.com
http://example.com:8080
不同源
端口不一样
第4点是大家要特别注意的一点,domain 跟subdomain 之间也是不同源的,所以 example.com
跟 www.example.com
不同源。有很多人常常会把这个跟cookie 搞混,因为 example.com
跟 www.example.com
是可以共用cookie 的。在这边特别强调,cookie 比对的规则叫做:Domain Matching ,它是看domain 而不是看我们这边所定义的origin,千万不要搞混了。
从以上范例可以得知,其实要达成同源满困难的,如果只看网址的话,基本上要长得一模一样,只有path 跟后面的部分可以不一样。
CORS)
已经知道了同源策略,与之相对的,如果你想实现在不同源之间进行HTTP传输数据的话,你应该怎么做?这规范就叫做CORS。
Run Chrome browser without CORS,其他浏览器的话就要自己查一下相关资料了。
把安全机制关掉以后,就可以顺利拿到response,浏览器也会跳一个提示出来:
问题是解决了,但为什么我说这是治标不治本呢?因为只有在你电脑上没问题而已,在其他人的电脑上面还是有问题。而且你关掉的不只是CORS,你连其他安全机制也一起关掉了。总之呢,只是跟大家介绍有这个解法,但不推荐使用。
http://localhost:91,跟 http://localhost:93 不同源):
xxxxxxxxxx
fetch('http://localhost:93').then(res => {
console.log('response', res);
return res.text();
}).then(body => {
console.log('body' , body)
})
你就会看到console 上面跳出显眼的红字:
前半段很熟悉,后半段可能就比较陌生一点。但没关系,我们看到了关键字:set the request's mode to 'no-cors'
,喔喔喔,难道说这样就可以不管CORS 吗?马上来试试看:
xxxxxxxxxx
fetch('http://localhost:93', {
mode: 'no-cors'
}).then(res => {
console.log('response', res);
return res.text();
}).then(body => {
console.log('body' , body)
})
添加mode: 'no-cors'
之后重新执行,果真不会跳错误出来了!console 一片干净,只是印出来的值似乎怪怪的:
Response 的status 是0,body 的内容是空的,type 是一个叫做 opaque
(不透明)的东西,看起来很奇怪。但如果我们切到Network 的那一个tab 去看,会发现其实后端是有回传response 的。所以设置这个mode 以后,并不会神奇地就让你可以突破限制拿到东西,正好相反,这个模式是在跟浏览器说:「我就是要发request 给一个没有cors header 的资源,我知道我拿不到response,所以你绝对不要给我response」。这就是一个自欺欺人的方法;
http://localhost:93/' from origin 'http://localhost:91' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
重点是这一句:No 'Access-Control-Allow-Origin' header is present on the requested resource
刚刚有提到说后端才是拥有权限的那一方,可以告诉浏览器:「我允许这个origin 跨来源存取我的资源」,而告诉浏览器的方法,就是在response 加上一个header。
这个header 的名称叫做Access-Control-Allow-Origin
,内容就是你想要放行的origin,例如说:Access-Control-Allow-Origin: http://127.0.0.1:91
,这样就是允许 http://127.0.0.1:91
的跨来源请求。
如果只想针对特定的origin 开放权限,只要传入要开放的origin 就行了。放*
,就代表允许任何origin 的意思。如果想要针对多个origin,server 那边必须做一点额外处理。
这才是从根本去解决跨来源请求的问题的方式,如果你跟存取资源的服务端有合作关系的话,直接请他们设定这个header 就行了。但是时候,你可能会想从「跟你没有合作关系」的服务端获取数据,例如说你想去抓 google.com 的内容之类的,这些资源绝对不会给你 Access-Control-Allow-Origin
这个header。
这时候怎么办呢?
让我们欢迎proxy server 登场!
Content-Type
头值须为application/x-www-form-urlencoded
、multipart/form-data
或text/plain
其中之一;
X-token
等)。Content-Type
的值非application/x-www-form-urlencoded
、multipart/form-data
、text/plain
其中之一;
带有自定义的 HTTP 标头(如X-token
)的请求;
执行修改服务器数据的方法时,例如PUT
、DELETE
、PATCH
方法。
现在我们写一个用JSON 的方式传数据到后端的代码:
浏览器运行后,切到Network tab 去看request 的状况,发现除了原本预期的POST 以外,还多了一个方法为OPTIONS 的预检请求,浏览器会帮忙带上两个header:
预检请求的作用就是查看服务器是否支持当前的跨域请求。预检请求的response 需要明确用 Access-Control-Allow-Headers
来表明:「我愿意接受这个header」,浏览器才会判断预检通过。
而在这个案例中,content-type
就属于自定义header,所以后端必须明确表示愿意接受这个header:
xxxxxxxxxx
res.header('Access-Control-Allow-Headers', 'Content-Type');
如此一来,前端那边就可以顺利通过preflight request,只有在通过preflight 之后,真正的那个request 才会真正发出。