浏览器跨域问题的几种解决方案

跨域问题出现的原因是由于浏览器的同源限制。

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

一个源的定义: 如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的源。

通常解决跨域问题的方法包括以下几种

  1. jsonp
  2. cors
  3. 降域
  4. postmessage

jsonp

原理

  • 首先是利用动态添加script标签,定制src属性来实现跨域
  • 通过将前端方法作为参数传递到服务器端,然后由服务器端注入参数后再返回,实现服务器端向客户端通信
  • 由于使用script标签的src属性,因此只支持get方法

jsonp简单实现

一个简单的jsonp实现,其实就是拼接url,然后动态的添加一个script元素到头部

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
35
36
37
function jsonp(req) {
var script = document.createElement('script')
var url = req.url + '?callback=' + req.callback.name
script.src = url
document.getElementsByTagName('head')[0].appendChild(script)
}

// 调用代码
function hello(res) {
console.log('hello ' + res.data)
}

jsonp({
url: "",
callback: hello
})

//服务器端代码
var http = require('http')
var urllib = require('url')

var port = 8080
var data = {'data': 'world'}

http.createServer(function(req,res){
var params = urllib.parse(req.url, true)
if(params.query.callback){
console.log(params.query.callback)
//jsonp
var str = params.query.callback + '(' + JSON.stringify(data) + ')'
res.end(str)
} else {
res.end()
}
}).listen(port, function(){
console.log('jsonp server is on')
})

上面的实现比较简单,有一些不足的地方

  1. 我们传递的回调必须是一个全局方法,要尽量减少全局方法使用
  2. 需要加入一些参数校验,确保接口正常执行

cors

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

  1. 浏览器先根据同源策略对前端页面和后台交互地址做匹配,若同源,则直接发送数据请求;若不同源,则发送跨域请求。
  2. 服务器解析程序收到浏览器跨域请求后,根据自身配置返回对应文件头。若未配置过任何允许跨域,则文件头里不包含Access-Control-Allow-origin 字段,若配置过域名,则返回Access-Control-Allow-origin + 对应配置规则里的域名的方式。
  3. 浏览器根据接收到的http文件头里的Access-Control-Allow-origin字段做匹配,若无该字段,说明不允许跨域;若有该字段,则对字段内容和当前域名做对比,如果同源,则说明可以跨域,浏览器发送请求;若不同源,则说明该域名不可跨域,不发送请求

降域

  1. 把child.a.com和a.com和xxx.child.a.com都可以降域为a.com。因此只能在不同子域名之间实现
  2. 实现的话需要在两个需要进行跨域的网站script都写document.domain = ‘a.com’进行降域
  3. 其中ajax不受降域的影响,得用iframe在页面中引用另一个页面的这种方式
  4. 降域有安全性的问题,如果一个子域名被攻击,多个被降域的域名都会被连带。

postmessage

HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,”通配符”表示不作限制

a.com/index.html中的代码
1
2
3
4
5
6
7
8
9
<iframe id="ifr" src="b.com/index.html"></iframe>
<script>
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样
// 若写成'http://c.com'就不会执行postMessage了
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b.com/index.html中的代码
1
2
3
4
5
6
7
8
9
10
<script>
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 弹出"I was there!"
alert(event.source); // 对a.com、index.html中window对象的引用
// 但由于同源策略,这里event.source不可以访问window对象
}
}, false);
</script>