什么是跨域?
1、 跨域是指一个域下的文档或脚本试图去请求另一个域下的资源(跨域是广义的),通常会在页面上使用ajax请求访问其他服务器的数据,此时,客户端会出现跨域问题。
广义的跨域:
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入: <link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
跨域原因:
1.形成跨域的原因:由于javascript语言安全限制中的同源策略造成的.
2.跨域问题的产生背景:早期为了防止CSRF(跨域请求伪造)的攻击,浏览器
引入了同源策略(SOP)来提高安全性
例如:
两个不同的域 a b
**在 a的应用的js脚本中调用了b的后端地址**
http://cas.feixue.com a
http://cart.feixue.com b
a: js
ajax请求的方式调用 --> http://index.com/......do
2、同源策略("协议+域名+端口"三者相同)是指一段脚本只能读取来自同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协议和端口号的组合,如下表所示:
URL | 说明 | 是否允许通信 |
---|---|---|
http://www.a.com/a.js 和 http://www.a.com/b.js | 同一域名下 | 允许 |
http://www.a.com/lab/a.js 和 http://www.a.com/script/b.js | 同一域名下不同文件夹 | 允许 |
http://www.a.com:8000/a.js 和 http://www.a.com/b.js | 同一域名,不同端口 | 不允许 |
http://www.a.com/a.js 和 https://www.a.com/b.js | 同一域名,不同协议 | 不允许 |
http://www.a.com/a.js 和 http://70.32.92.74/b.js | 域名和域名对应相同ip | 不允许 |
http://www.a.com/a.js和 http://script.a.com/b.js | 主域相同,子域不同 | 不允许 |
http://www.a.com/a.js 和 http://a.com/b.js | 同一域名,不同二级域名(同上) | 不允许cookie这种情况下也不允许访问) |
http://www.cnblogs.com/a.js 和 http://www.a.com/b.js | 不同域名 | 不允许 |
3、解决跨域方法:
1)、通过jsonp跨域:
缺点:jsonp缺点:只能实现get一种请求;
通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信。
原理:假设,我们源页面是在a.com,想要获取b.com的数据,我们可以动态插入来源于b.com 的脚本
① - 原生Js:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.b.com:8080/login?user=admin&callback=onBack';
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
- 服务端返回如下(返回时即执行全局函数):
onBack({"status": true, "user": "admin"})
② - jQuery ajax:
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "onBack", // 自定义回调函数名
data: {}
});
③- Vue:
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'onBack'
}).then((res) => {
console.log(res);
})
④-node.js代码示例:
var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
var params = qs.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 3000...');
2)、document.domain + iframe跨域:
注:此方案仅限主域相同,子域不同的跨域应用场景
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1.)父窗口:(http://www.domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
==================================================================================
2.)子窗口:(http://child.domain.com/b.html)
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
alert('get js data from parent ---> ' + window.parent.user);
</script>
3)、服务器跨域
在实践过程中,一般我们喜欢让服务器来多做一些处理,从而尽可能让前端简化。这里将介绍两种常用的方法:反向代理和CORS。
- 反向代理
所谓反向代理服务器,它是代理服务器中的一种。客户端直接发送请求给代理服务器,然后代理服务器会根据客户端的请求,从真实的资源服务器中获取资源返回给客户端。所以反向代理就隐藏了真实的服务器。利用这种特性,我们可以通过将其他域名的资源映射成自己的域名来规避开跨域问题
跨域资源共享(CORS):
-
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置,若要带cookie请求:前后端都需要设置。
-
需注意的是:由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页。如果想实现当前页cookie的写入。
-
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
-
简单请求需要满足以下几个条件:
1.请求方法只允许:GET,HEAD,POST;
2.对于请求头字段有严格的要求,一般情况下不会超过以下几个字段:
Accept
Accept-Language
Content-Language
Content-Type
3.当发起POST请求时,只允许Content-Type为application/x-www-form-urlencoded,multipart/form-data,text/plain。
对于简单请求来说,服务器和客户端之间的通信只是进行简单的交换
1、 前端设置:
① 原生ajax
// 前端设置是否带cookie
xhr.withCredentials = true;
eg:
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
// 前端设置是否带cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
② jQuery ajax
$.ajax({
...
xhrFields: {
withCredentials: true // 前端设置是否带cookie
},
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
③ vue框架
a.) axios设置:
axios.defaults.withCredentials = true
b.) vue-resource设置:
Vue.http.options.credentials = true
2、 服务端设置:
若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。
4)、window.postMessage
-
postMessage是HTML5新增在window对象上的方法,目的是为了解决在父子页面上通信的问题。该技术有个专有名词:跨文档消息(cross-document messaging)。利用postMessage的特性可以实现较为安全可信的跨域通信。
-
postMessage方法接受两个参数:
1.message: 要传递的对象,只支持字符串信息,因此如果需要发送对象,可以使用JSON.stringify和JSON.parse做处理
2.targetOrigin: 目标域,需要注意的是协议,端口和主机名必须与要发送的消息的窗口一致。如果不想限定域,可以使用通配符“*”,但是从安全上考虑,不推荐这样做。
下面介绍一个例子:
首先,先创建一个demo的html文件,我们这里采用的是iframe的跨域,当然也可以跨窗口。
<p>
<button id="sendMsg">sendMsg</button>
</p>
<iframe id="receiveMsg" src="http://b.html">
</iframe>
然后,在sendMsg的按钮上绑定点击事件,触发postMessage方法来发送信息给iframe:
window.onload = function() {
var receiveMsg = document.getElementById('receiveMsg').contentWindow; //获取在iframe中显示的窗口
var sendBtn = document.getElementById('sendMsg');
sendBtn.addEventListener('click', function(e) {
e.preventDefault();
receiveMsg.postMessage('Hello world', 'http://b.html');
});
}
接着,你需要在iframe的绑定的页面源中监听message事件就能正常获取消息了。其中,MessageEvent对象有三个重要属性:data用于获取数据,source用于获取发送消息的窗口对象,origin用于获取发送消息的源。
window.onload = function() {
var messageBox = document.getElementById('messageBox');
window.addEventListener('message', function(e) {
//do something
//考虑安全性,需要判断一下信息来源
if(e.origin !== 'http://xxxx') return;
messageBox.innerHTML = e.data;
});
}
总得来说,postMessage的使用十分简单,在处理一些和多页面通信、页面与iframe等消息通信的跨域问题时,有着很好的适用性.