CORS 是一个 W3C 标准,全称"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求(处理复杂请求的"预检"请求),但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
只要同时满足以下两大条件,就属于简单请求
使用下列请求方法之一:
GET
HEAD
POST
都是 HTTP/1.0 所支持的 HTTP 请求方法
HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
其中 Content-Type 的值仅限于下列三者之一:
text/plain
multipart/form-data
application/x-www-form-urlencoded
表单或文本
对于简单请求,浏览器检测到请求跨域时,直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个 Origin 字段:
transfer | 可选属性
是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权
接收数据:监听 message 事件的发生
window.addEventListener("message", receiveMessage, false) ;
function receiveMessage(event) {
var origin= event.origin;
console.log(event);
}
message 事件包含的 event 对象有以下四个属性:
data:指的是从其他窗口发送过来的消息对象;
type:指的是发送消息的类型;
source:指的是发送消息的窗口对象;
origin:指的是发送消息的窗口的源;
举例
假设 http://localhost:3000/a.html 页面需要向 http://localhost:4000/b.html 传递数据,并接收 b 页面返回的数据,需要跨域操作。
在 a 页面中,我们内嵌一个 iframe 窗口,去主动加载 b 页面,并设置一个 onload 事件,当 iframe 窗口加载 b 页面完成后,触发注册的事件回调函数,里面可以设置向 b 页面的数据发送以及对 b 页面传回数据的监听响应。
同样的,在 b 页面中,我们需要监听 message 事件,做一些响应和传递数据的操作。
参考文章: 九种跨域方式实现原理(完整版)
同源策略
定义
同源策略是一种约定,是浏览器端的实现,是浏览器最核心也最基本的安全功能。 同源 = 「协议 + 域名 + 端口」完全相同
http://(协议)www(子域名).abc.com(主域名):8080(端口号)/xxx
作用
减少浏览器遭受到 XSS、CSRF 等攻击的可能性。 本质上是因为同源策略限制了:
可跨域加载资源的标签
同源策略限制了浏览器端对资源的跨域获取,但是有三个标签是被允许跨域加载资源的:
<img src="...">
<link href="...">
<script src="...">
常见的跨域场景
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。 对于跨域问题,需要注意几点:
请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。 你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会? 因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
跨域解决方案
JSONP
原理
利用
<script>
标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。 JSONP 请求一定需要对方的服务器做支持才可以。优缺点
优点:简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。 缺点:仅支持 get 方法,具有局限性;不安全,可能会遭受 XSS 攻击。
实现流程
<script>
标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。前端代码实现
后端代码实现
JSONP & AJAX
JSONP 和 AJAX 相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但 AJAX 属于同源策略,JSONP 属于非同源策略(跨域请求)。此外,AJAX 支持多种数据请求的方式,而 JSONP 只支持 get 请求。
CORS
参考:跨域资源共享 CORS 详解
概念
CORS 是一个 W3C 标准,全称"跨域资源共享"(Cross-origin resource sharing)。 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求(处理复杂请求的"预检"请求),但用户不会有感觉。 因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。 虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。
简单请求
只要同时满足以下两大条件,就属于简单请求
使用下列请求方法之一:
HTTP的头信息不超出以下几种字段:
其中 Content-Type 的值仅限于下列三者之一:
对于简单请求,浏览器检测到请求跨域时,直接发出 CORS 请求。具体来说,就是在头信息之中,增加一个 Origin 字段:
请求头信息中的 Origin 字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
服务端接收到 CORS 请求,会在服务端域名白名单中查找该 Origin,如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
响应 CORS 的头信息之中,有三个与 CORS 请求相关的字段,都以
Access-Control-
开头(都可在服务器端进行配置):如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,被 XMLHttpRequest 的 onerror 回调函数捕获。
关于跨域场景下 Cookie 的传输,还有必要进一步的说明:CORS 请求默认不发送 Cookie 和 HTTP 认证信息。如果要把 Cookie 发到服务器,一方面要服务器同意,指定 Access-Control-Allow-Credentials 字段为 true:
Access-Control-Allow-Credentials: true
,另一方面,开发者必须在 AJAX 请求中打开withCredentials
属性:否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
需要注意的是,如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的Cookie。
复杂请求
简单请求之外的都算作复杂请求。 复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求。 浏览器会先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 预检请求的 HTTP 头信息如下:
"预检"请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是 Origin,表示请求来自哪个源。 除了Origin字段,"预检"请求的头信息包括两个特殊字段。
服务器收到"预检"请求以后,在配置中检查 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 字段以后,确认允许跨源请求,就可以做出回应。如果服务器否定了"预检"请求,会返回一个正常的 HTTP 回应,不包含任何的 CORS 相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest 对象的 onerror 回调函数捕获。 服务器正确响应 CORS 预检请求时,会返回如下响应头信息:
其中,
Access-Control-Allow-Origin
字段最关键,它告诉浏览器其字段值下的域名可以请求数据,若字段值为*
表示同意任意跨源请求。 此外,关于复杂请求还有一些其他的 CORS 响应字段:一旦服务器通过了"预检"请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。详细头信息请参考简单请求。
CORS & JSONP
CORS 与 JSONP 的使用目的相同,但是比 JSONP 更强大。 JSONP 只支持 GET 请求,CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。
postMessage
参考:postMessage可太有用了
postMessage 是 html5 引入的 API, postMessage() 方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档,多窗口,跨域消息传递.多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案.
postMessage API
发送数据:调用 postMessage API
接收数据:监听 message 事件的发生
message 事件包含的 event 对象有以下四个属性:
举例
假设
http://localhost:3000/a.html
页面需要向http://localhost:4000/b.html
传递数据,并接收 b 页面返回的数据,需要跨域操作。 在 a 页面中,我们内嵌一个 iframe 窗口,去主动加载 b 页面,并设置一个 onload 事件,当 iframe 窗口加载 b 页面完成后,触发注册的事件回调函数,里面可以设置向 b 页面的数据发送以及对 b 页面传回数据的监听响应。 同样的,在 b 页面中,我们需要监听 message 事件,做一些响应和传递数据的操作。WebSocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信。由于 WebSocket 本身不存在跨域问题,所以我们可以利用 WebSocket 来进行非同源之间的通信。
原理
WebSocket 和 HTTP 都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。
Node 中间件代理
原理
同源策略是浏览器需要遵循的标准,但服务器向服务器请求就无需遵循同源策略。因此我们可以设置代理服务器来转发浏览端向服务端的请求。 代理服务器的工作职责:
浏览器和代理服务器之间的数据交互要遵循同源策略,因此代理服务器要和浏览器同源,但服务器之间不需要遵循同源策略,因此代理服务器可以自由地向其他服务器进行数据请求和响应。代理服务器充当一个中间人角色,专门处理浏览器和服务器间不能直接进行的操作。
nginx 反向代理
实现原理类似于 Node 中间件代理, nginx 会将同源的本地请求收集起来,并分发到各个服务器中,并将服务器响应的结果收集并返回。与 node 中间件代理的区别在于,nginx 更偏向底层服务端的逻辑处理,因此它实际上与服务端处于统一内网中,通过设置前端服务的域名,获得一个跳板机保证同源访问,将访问请求代理到设置的后端服务域名中。 利用 nginx 解决跨域问题,需要你搭建一个中转 nginx 服务器,用于转发请求。
优势
使用 nginx 反向代理实现跨域,是最简单的跨域方式
正向代理
正向代理会把访问某网站服务器 server 的网页请求,代理到一个可以访问该网站的代理服务器 proxy,这个代理服务器 proxy 把该网站服务器 server 上的网页内容获取,再转发给客户。
反向代理
在反向代理中,客户端向服务端发送的请求,会先被代理服务器proxy接收,这个代理服务器将把请求代理到和自己属于同一个LAN(局域网)下的内部服务器上。与正向代理不同的是,反向代理的对象不是客户端而是服务端。反向代理服务器向客户端提供了一个统一的代理入口,客户端的请求,都先经过这个proxy服务器,至于在内网真正访问哪台服务器内容,由这个proxy去控制,因此,客户端经过反向代理,是无法选择具体请求哪台服务器的。
反向代理优势
window.name + iframe
原理
window.name 属性特点:name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)。 这就意味着我们可以用 window.name 作为不同域间数据的载体,通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
实例
假设我们需要从 a 页面跨域请求 c 页面中的数据,其中 a.html 和 b.html 是同域的,都是
http://localhost:3000
; 而 c.html 是http://localhost:4000
。 b.html 为中间代理页,与 a.html 同域,内容为空。我们可以将 b 页面看作跨域数据的一个载体,用一个空页面当作仓库,c 页面将数据通过 window.name 保存在这个 b 仓库中,a 页面则通过 window.name 从该仓库中取出数据。> 页面的请求访问实际是用 iframe 做了替代,而数据的流转则用了不受同源策略限制的 window.name 属性
location.hash + iframe
原理
通过在 url 的 hash 值传递数据,一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。
实例
document.domain + iframe
该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。
实现原理
两个页面都通过 js 强制设置 document.domain 为基础主域,就实现了同域。