前端跨域的整理
跨域资源共享 CORS
对于web开发来讲,由于浏览器的同源策略,我们需要经常使用一些hack的方法去跨域获取资源,但是hack的方法总归是hack。直到W3C出了一个标准-CORS-”跨域资源共享”(Cross-origin resource sharing)。
两种请求CORS 的请求分两种,这也是浏览器为了安全做的一些处理,不同情况下浏览器执行的操作也是不一样的,主要分为两种请求,当然这一切我们是不需要做额外处理的,浏览器会自动处理的。 简单请求(simple request)只要同时满足以下两大条件,就属于简单请求。 条件1) 请求方法是以下三种方法中的一个: HEAD GET POST 2)HTTP的头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain 过程对于简单的跨域请求,浏览器会自动在请求的头信息加上 Origin 字段,表示本次请求来自哪个源(协议 + 域名 + 端口),服务端会获取到这个值,然后判断是否同意这次请求并返回。 // 请求 GET /cors HTTP/1.1 Origin: http://api.qiutc.me Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 1.服务端允许如果服务端许可本次请求,就会在返回的头信息多出几个字段: // 返回 Access-Control-Allow-Origin: http://api.qiutc.me Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: Info Content-Type: text/html; charset=utf-8 这三个带有 Access-Control 开头的字段分别表示:
2.服务端拒绝
当然我们为了防止接口被乱调用,需要限制源,对于不允许的源,服务端还是会返回一个正常的HTTP回应,但是不会带上 Access-Control-Allow-Origin 字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequest 的 onerror 回调捕获到。 非简单请求条件出了简单请求以外的CORS请求。 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。 过程1)预检请求非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。 预检请求的发送请求: OPTIONS /cors HTTP/1.1 Origin: http://api.qiutc.me Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.qiutc.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... “预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。 除了Origin字段,”预检”请求的头信息包括两个特殊字段。
2)浏览器的正常请求和回应一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。 参考:《跨域资源共享 CORS 详解》http://www.ruanyifeng.com/blog/2016/04/cors.html 阮大神的文章,复制粘贴了不少。 jsonpjsonp = json + padding 其实对于常用性来说,jsonp应该是使用最经常的一种跨域方式了,他不受浏览器兼容性的限制。但是他也有他的局限性,只能发送 GET 请求,需要服务端和前端规定好,写法丑陋。 它的原理在于浏览器请求 script 资源不受同源策略限制,并且请求到 script 资源后立即执行。 主要做法是这样的:
在一些第三方库往往都会封装jsonp的操作,比如 jQuery 的$.getJSON。 document.domain一个页面框架(iframe/frame)之间(父子或同辈),是能够获取到彼此的window对象的,但是这个 window 不能拿到方法和属性(尼玛这有什么用,甩脸)。 // 当前页面域名 http://blog.qiutc.me/a.html <script> function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; // 这里可以获取 iframe 里面 window 对象,但是几乎没用 var doc = iframeWindow.document; // 获取不到 } </script> <iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe> 这个时候,document.domain 就可以派上用场了,我们只要把 http://blog.qiutc.me/a.html 和 http://www.qiutc.me/b.html 这两个页面的 document.domain 都设成相同的域名就可以了。 前提条件:这两个域名必须属于同一个基础域名!而且所用的协议,端口都要一致。 但要注意的是,document.domain 的设置是有限制的,我们只能把 document.domain 设置成自身或更高一级的父域,且主域必须相同。例如:a.b.example.com 中某个文档的 document.domain 可以设成a.b.example.com、b.example.com、example.com中的任意一个,但是不可以设成 c.a.b.example.com,因为这是当前域的子域,也不可以设成baidu.com,因为主域已经不相同了。 这样我们就可以通过js访问到iframe中的各种属性和对象了。 // 主页面:http://blog.qiutc.me/a.html <script> document.domain = 'qiutc.me'; function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; // 这里可以获取 iframe 里面 window 对象并且能得到方法和属性 var doc = iframeWindow.document; // 获取到 } </script> <iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe> // iframe 里面的页面 <script> document.domain = 'qiutc.me'; </script> window.namewindow对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。 比如有一个www.qiutc.me/a.html页面,需要通过a.html页面里的js来获取另一个位于不同域上的页面www.qiutc.com/data.html里的数据。 data.html页面里的代码很简单,就是给当前的window.name设置一个a.html页面想要得到的数据值。data.html里的代码: <script> window.name = '我是被期望得到的数据'; </script> 那么在 a.html 页面中,我们怎么把 data.html 页面载入进来呢?显然我们不能直接在 a.html 页面中通过改变 window.location 来载入data.html页面(这简直扯蛋)因为我们想要即使 a.html页面不跳转也能得到 data.html 里的数据。 答案就是在 a.html 页面中使用一个隐藏的 iframe 来充当一个中间人角色,由 iframe 去获取 data.html 的数据,然后 a.html 再去得到 iframe 获取到的数据。 充当中间人的 iframe 想要获取到data.html的通过window.name设置的数据,只需要把这个iframe的src设为www.qiutc.com/data.html就行了。然后a.html想要得到iframe所获取到的数据,也就是想要得到iframe的window.name的值,还必须把这个iframe的src设成跟a.html页面同一个域才行,不然根据前面讲的同源策略,a.html是不能访问到iframe里的window.name属性的。这就是整个跨域过程。 // a.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> function getData() { var iframe =document.getElementById('iframe'); iframe.onload = function() { var data = iframe.contentWindow.name; // 得到 } iframe.src = 'b.html'; // 这里b和a同源 } </script> </head> <body> <iframe src="http://www.qiutc.com/data.html" style="display:none" onload="getData()"</iframe> </body> </html> window.postMessagewindow.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。兼容性:
http://caniuse.com/#search=postMessage 调用postMessage方法的window对象是指要接收消息的那一个window对象,该方法的第一个参数message为要发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * 。 需要接收消息的window对象,可是通过监听自身的message事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。 上面所说的向其他window对象发送消息,其实就是指一个页面有几个框架的那种情况,因为每一个框架都有一个window对象。在讨论第种方法的时候,我们说过,不同域的框架间是可以获取到对方的window对象的,虽然没什么用,但是有一个方法是可用的-window.postMessage。下面看一个简单的示例,有两个页面: // 主页面 blog.qiutc.com <script> function onLoad() { var iframe =document.getElementById('iframe'); var iframeWindow = iframe.contentWindow; iframeWindow.postMessage("I'm message from main page."); } </script> <iframe src="http://www.qiutc.me/b.html" onload="onLoad()"</iframe> // b 页面 <script> window.onmessage = function(e) { e = e || event; console.log(e.data); } </script> CSST (CSS Text Transformation)一种用 CSS 跨域传输文本的方案。 优点:相比 JSONP 更为安全,不需要执行跨站脚本。 缺点:没有 JSONP 适配广,CSST 依赖支持 CSS3 的浏览器。 原理:通过读取 CSS3 content 属性获取传送内容。 具体可以参考:CSST (CSS Text Transformation) 利用flashflash 嘛,这个东西注定灭亡,不想说了。。。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |