1、什么是跨域

在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服浏览器同源限制

1.1 什么是同源策略?

同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM和JS对象无法获得
  • xhr请求不能发送

2、常见的跨域场景































URL 说明 是否允许通信

1. www.domain.com/a.js
2. www.domain.com/b.js
3. www.domain.com/lab/c.js
同一域名,不同文件或路径 允许

1. www.domain.com:8000/a.js
2. www.domain.com/b.js
同一域名,不同端口 不允许

1. http://www.domain.com/a.js
2. http://192.168.4.12/b.js
域名和域名对应相同ip 不允许

1. http://www.domain.com/a.js
2. http://x.domain.com/b.js
3. http://domain.com/c.js
主域相同,子域不同 不允许

3、跨域的解决方案

3.1 JSONP跨域

jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

缺点:jsonp的缺点:只能发送get一种请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 前端
<script>
var script = document.createElement('script');
script.type = 'text/javascript';

// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);

// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>

// nodejs 服务端
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;

// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');

res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

3.2 跨域资源共享(CORS)

 CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

浏览器将CORS跨域请求分为简单请求和非简单请求。

A、只要同时满足一下两个条件,就属于简单请求:

  1. 使用下列方法之一:head、get、post
  2. 请求的Heder是:
    1. Accept
    2. Accept-Language
    3. Content-Language
    4. Content-Type: 只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

B、复杂请求:
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

预检请求

 预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。

1)Access-Control-Request-Method:必选

2)Access-Control-Request-Headers:可选

3.3 nginx代理跨域

nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin等字段。

1)nginx配置解决iconfont跨域

浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

2)nginx反向代理接口跨域

3.4 nodejs中间件代理跨域

3.5 document.domain + iframe跨域

此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 父窗口
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>


// 子窗口
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
console.log('get js data from parent ---> ' + window.parent.user);
</script>

3.6 postMessage跨域

 postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套的iframe消息传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// a.html (www.domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};

// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>


// b.html (www.domain2.com/b.html)
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);

var data = JSON.parse(e.data);
if (data) {
data.number = 16;

// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>



参考文档