跨域对于前端来讲是个耳熟的字眼,在日常工作和面试中经常出现。
本文介绍了同源策略
、CORS跨域资源共享
、实际开发中如何处理跨域
这几个方面。
前端开发过程中,你肯定见过这个报错。Access to fetch at 'https://m.demo.com' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
这就是跨域请求被浏览器拦截没跑了。
same origin policy & CORS
同源策略 (same origin policy)
,是浏览器最核心、最基本的安全功能之一。它限制了从一个源(origin)
加载的文档如何与另外一个源的资源进行交互。
Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头。
更详细的说,是浏览器对在脚本内跨源发起的 http请求 的response结果进行了拦截。
想象一下,如果没有这个策略限制,就会发生某一个源随意调用其他源的接口获取他人数据,篡改他们数据……等等肥肠价值观警告的事情。
该策略常见的三种情况:
- Cross-origin writes 跨域写 例如链接links、重定向、表单提交……
- Cross-origin embedding 资源嵌入 比如 script 、link、img、video等标签嵌入资源
- Cross-origin reads 跨域读 但可通过嵌入巧妙绕过
同源
就是指两个页面的协议、主机、端口号(如果有)三者皆一致。 当一个资源a从一个与该资源a所在服务器a不同协议、域或端口 的服务器b上请求另一个资源b,这就发起了一个跨域HTTP请求。
exp:http:/ / www. baidu. com:80 http即协议,www. baidu . com即主机,80即端口号。
但有时候开发中,确实存在这样、那样的跨域资源获取需求。这就要讲到跨域资源共享(Cross-Origin Resource Sharing)
,允许服务器声明 哪些源 有权限通过浏览器 访问哪些资源。给我们争取一个跨域请求的机会。
借用个栗子,客户端发起一个simple request get请求:
-
Client
request header
请求头 get 请求获取 /resources/public-data/ 下的资源
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/\*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http:\/\/foo.example\/examples\/access-control\/simpleXSInvocation.html
Origin: http:\/\/foo.example
-
Server
response header
响应头
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[XML Data]
服务端返回的 Access-Control-Allow-Origin: *
表明,该资源可被任意外域访问。
ps:如果服务器仅希望某个源(比如http:/ /examples.com)可以访问其资源,只需设置 Access-Control-Allow-Origin: http:/ /examples.com 。如果希望仅允许GET请求或多种请求,则可设置 Access-Control-Allow-Methods:GET, POST, ……
。
解决方法
已知的一些方法在生产环境几乎都需要server
进行配合,本地开发的话前端是完全可以自行绕过跨源问题。
方法如下(前四种适用于生产环境):
1、后端在reponse header中添加 Access-Control-Allow-Origin: xxx
允许跨源请求。
exp,http: //hello-world.example 想访问 http: //example.com 的资源。 http: //example.com 在response header 中添加
Access-Control-Allow-Origin: http: //hello-world.example
。
当然 也可以一了百了添加Access-Control-Allow-Origin: *
,只要你确保不会产生其他副作用。
2、前端使用JSONP。
像一些资源标签(如img,video,script……)允许跨源资源嵌入,即标签的src是允许嵌入跨源资源的。
jsonp
其实就是通过 script src 传递参数(请求参数、约定的callback name)给服务器,服务器将返回数据包裹在回调函数中,直接在脚本中进行调用达到跨域获取资源数据的效果。
3、document.domain,允许子域安全访问其父域。
document.domain
的值可以设置为其当前域或其当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。(设置后当前域端口号会被重写为Null)。
举个栗子,假设http: //demo.example.com中有一个脚本执行了 document.domain = 'example.com'; 那么浏览器将会通过对http: //company.com/dir/page.html (http: //company.com/dir/page.html 也必须设置document.domain = 'example.com'; 以重置端口号)资源的同源检测。
4、OPTIONS预检请求
preflight request 是一个包含了:
Access-Control-Request-Method
和Access-Control-Request-Headers
,以及一个Origin
首部的options请求。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。
对于可能对服务器数据产生副作用的请求(GET以外,搭配某些MIME type的请求),浏览器都会先发出一个预检请求,以监测服务器是否允许该类型的请求。确认允许后,才会发出实际请求。
OPTIONS 请求用于获取目的资源所支持的通信选项。
5、如果是本地开发,还可使用chrome - Allow CORS: Access-Control-Allow-origin插件允许跨域,或命令行 open -a "Google Chrome" --args --disable-web-security --user-data-dir
。
chrome插件的做法应该就是在 response 达到浏览器前,在 response header 中添加了 Access-Control-Allow-Origin 和 Access-Control-Allow-Methods 两个字段来达到允许跨域资源共享的原理。
6、webpack proxy
可以在开发环境代理跨域请求,但build打包后,代理是不生效的,请注意这点。
参考资料
CORS:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
浏览器同源策略:https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
Document.domain:https://developer.mozilla.org/zh-CN/docs/Web/API/Document/domain
W3C Cross-Origin Resource Sharing:https://www.w3.org/TR/cors/
如有遗漏的方法 欢迎补充讨论 ~