跨域问题总结

1.浏览器的同源策略

浏览器的同源策略,它用于限制一个源的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

2.同源的定义

如果两个url的协议/端口/主机都相同的话,则这两个url是同源,这个方案也被称为“协议/主机/端口元组”,或者直接是“元组”

下表给出与url:”http://store.company.com/dir/page.html" 的源进行对比示例:

URL 结果 原因
http://store.company.com/dir2/other.html 同源 只有路径不同
http://store.company.com/dir/inner/another.html 同源 只有路径不同
https://store.company.com/secure.html 失败 协议不同
http://store.company.com:81/dir/etc.html 失败 端口不同 ( http:// 默认端口是80)
http://news.company.com/dir/other.html 失败 主机不同

一下是可能嵌入跨域资源的示例:

  • <script src="..."></script>标签嵌入跨域脚本
  • <link rel="stylesheet" href="">标签嵌入css
  • 通过<img>展示的图片
  • 通过<video><audio>播放的多媒体资源
  • 通过<object> <embed> <applet>嵌入的插入就
  • 通过@font-face 引入的字体。一些浏览器允许跨域字体,一些需要同源字体
  • 通过<iframe>载入的任何资源。站点可以使用 X-Frame-Options 消息头来阻止这种形式的跨域交互。

3.跨域解决方案

1.jsonp
2.ajax
3.CORS
4.document.domain + iframe
5.window.postMessage()
6.window.name + iframe
7.nginx代理

1.jsonp跨域
原理:动态生成一个script标签,插入head中,浏览器会执行script标签中的代码,但是只能使用get请求

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
var script = document.createElement('script')
script.type = 'text/javascript'

script.src = 'http://www.example.com?name=michael&callback=onCallback'
document.head.appendChild(script)

function onCallback(res) {
console.log(JSON.stringify(res))
// 处理数据
}
</script>

服务器返回后执行onCallback 函数

2.Ajax请求

1
2
3
4
5
6
7
$.ajax({
url: 'http://www.example.com',
type: 'get',
dataType: 'jsonp',
jsonpCallback: 'onCallback',
data: {}
})

3.CORS方式

Cross-Origin Resource Sharing可以使用cors来允许跨域访问。cors是http的一部分,它允许服务端来指定哪些主机可以从这个服务端加载资源。

前端配置withCredentials:true,设置提供凭据信息(cookie、HTTP认证及客户端SSL证明等),当前请求为跨域类型时是否在请求中协带cookie。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-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
    凡是不同时满足上面两个条件,就属于非简单请求。

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。origin字段用来说明本次请求用哪个源(协议,端口,域名),服务器根据这个值判断是否接受这次请求。
Access-Control-Allow-Origin:它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求
Access-Control-Allow-Credentials:它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器

withCredentials 属性:
CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。Access-Control-Allow-Credentials: true。另一方面,开发者必须在AJAX请求中打开withCredentials属性,xhr.withCredentials = true;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。

非简单请求:非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。Access-Control-Request-Method用来列出浏览器的CORS请求会用到哪些HTTP方法

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据

4.Document.domain + iframe

条件:
页面 http://www.example.com/a.html

页面中有一个iframe http://iframe.com/b.html

方式:
将两个页面的document.domain 设置成相同的域名,就可以在页面中拿到iframe中的数据

限制:
只能把document.domain设置成自身或更高一级的父域

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
//a.html
document.domain = 'example.com';
var iframe = document.createElement('iframe');
iframe.src = 'http://iframe.com/b.html';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.onload = function() {
var doc = iframe.contentDocument || iframe.contentWindow.document;
console.log(doc)
}

//b.html
document.domain = 'example.com';

5.window.postMessage()
HTML5的新特性,允许来自不同源的脚本采用异步方式进行通信,实现跨域传递消息

使用方法:

postMessage(data, origin)

data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化

origin: 协议+主机+端口号,也可以设置为”*”,表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/“

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// http://www.example.com/a.html
<iframe src="http://www.example2.com/b.html" style="display: none;" id="ifr"></iframe>
<script>
var iframe = document.getElementById('ifr');
iframe.onload = function() {
var data = {};
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.example2.com');
};

// 接受传输的数据
window.addEventListener('message', function(e) {
console.log(e.data);
}, false);
</script>

// http://www.example2.com/b.html

<script>
window.addEventListener('message', function(e) {
console.log(e.data);
})
</script>

6.window.name + iframe
原理:window.name 在不同的页面加载后依然存在,最大为2M