什么是浏览器的同源策略
同源指的是”协议+域名+端口”三者相同,即使两个不同的域名指向同一个ip地址,也非
同源。同源策略是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到
XSS,csrf攻击。
同源策略限制以下几种行为:
- Cookie、LocalStorage和IndexDB无法读取
- Dom和JS对象无法获得
- AJAX请求不能发送
常见的跨域场景:
URL | 说明 | 是否允许通信 |
http://www.domain.com/a.js |
http://www.domain.com/b.js |
http://www.domain.com/lab/c.js | 同一域名,不同文件或路径 | 允许 |
http://www.domain.com:8000/a.js |
http://www.domain.com/b.js | 同一域名,不同端口 | 不允许 |
http://www.domain.com/a.js |
https://www.domain.com/b.js | 同一域名,不同协议 | 不允许 |
http://www.domain.com/a.js |
http://192.168.4.12/b.js | 域名和域名对应相同ip | 不允许 |
http://www.domain.com/a.js |
http://x.domain.com/b.js |
http://domain.com/c.js | 主域相同,子域不同 | 不允许 |
http://www.domain1.com/a.js |
http://www.domain2.com/b.js | 不同域名 | 不允许 |
主域名和子域名:
主域名: abc.com
wwww.abc.com // www是子域名
bbs.abc.com // bbs是主域名
beijing.bbs.abc.com //beijing.bbs是子域名
haidian.beijing.bbs.abc.com //haidian.beijing.bbs是子域名
主域名是不带www的域名,例如a.com,主域名前面带前缀的通常都为二级域名或多级域名,例如www.a.com其实是二级域名。
跨域解决方案
- 通过jsonp实现跨域
- 跨域资源共享(CORS)
- document.domain+ iframe跨域
- location.hash + iframe
- window.name +iframe
- postMessage跨域
- nginx代理跨域
- nodexjs中间件代理跨域
- websocket协议跨域
一、jsonp实现跨域
-
原理
虽然浏览器默认禁止了跨域访问,但是并不禁止在页面中的script标签引用其他域下的JS文件。
根据这一点,可以方便地通过动态创建script节点的方法来实现完全跨域的通信。
-
代码实现
- 原生
<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>
- vue.js
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).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 8080...');
- 缺点
只能实现get一种请求
跨域资源共享(CORS)
普通跨域请求只需服务端设置Access-Control-Allow-Origin即可,前端无需设置,若要
带cookie请求,则前后端要要设置cookie相关配置。由于同源策略的限制,所读取的cookie为
跨域请求接口所在的cookie,而非当前页。若想实现当前页cookie的写入,可以参考下面的方法:
nginx反向代理中设置proxy_cookie_domain和NodeJs中间件代理中cookieDomainRewrite参数的设置。
目前,所有的浏览器都支持该功能,cors已经成为主流的跨域解决方案。
- 前端设置
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);
}
};
- 后端设置
var http = require('http');
var server = http.createServer();
var qs = require('querystring');
server.on('request', function(req, res) {
var postData = '';
// 数据块接收中
req.addListener('data', function(chunk) {
postData += chunk;
});
// 数据接收完毕
req.addListener('end', function() {
postData = qs.parse(postData);
// 跨域后台设置
res.writeHead(200, {
'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie
'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口)
/*
* 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
* 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
*/
'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie
});
res.write(JSON.stringify(postData));
res.end();
});
});
server.listen('8080');
console.log('Server is running at port 8080...');
document.domain + iframe跨域
此方案仅限主域相同,而子域不同的跨域应用场景.
实现原理: 两个页面都通过js强制设置document.domain为基础主域,就实现了同域.
- 父窗口: (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>
- 子窗口: (http://www.child.domain.com/b.html)
<script> document.domain = 'domain.com'; // 获取父窗口中变量 alert('get js data from parent ---> ' + window.parent.user); </script>
location.hash + iframe 跨域
- 实现原理
a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信.
同时,改变hash值并不会导致页面刷新。
- 代码实现
a.html(http://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');
// 向b.html传hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 开放给同域c.html的回调方法
function onCallback(res) {
alert('data from c.html ---> ' + res);
}
</script>
b.html:(http://www.domain2.com/b.html)
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 监听a.html传来的hash值,再传给c.html
window.onhashchange = function () {
// 这里的hash可以更改为要向a.html传递的数据
iframe.src = iframe.src + location.hash;
};
</script>
c.html:(http://www.domain1.com/c.html)
<script>
// 监听b.html传来的hash值
window.onhashchange = function () {
// 再通过操作同域a.html的js回调,将结果传回
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
- 缺点
- 数据直接暴露在url中,不安全
- url有长度的限制,因此传递的数据有限
参考文章
- https://segmentfault.com/a/1190000015764619
- https://segmentfault.com/a/1190000011145364