什么是跨域
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域,域名对应的IP地址也算是跨域操作。不同域之间相互请求资源,就算作“跨域”。
有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。
同源策略限制内容有:
1、Cookie、LocalStorage、IndexedDB等存储性内容
2、不允许进行DOM节点的操作
3、不能进行AJAX请求
常见的跨域场景
http://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 非跨域
http://www.nealyang.cn/index.html 调用 http://www.neal.cn/server.php 跨域,主域不同
http://abc.nealyang.cn/index.html 调用 http://def.neal.cn/server.php 跨域,子域名不同
http://www.nealyang.cn:8080/index.html 调用 http://www.nealyang.cn/server.php 跨域,端口不同
https://www.nealyang.cn/index.html 调用 http://www.nealyang.cn/server.php 跨域,协议不同
localhost 调用 127.0.0.1 跨域
跨域的解决方法
1.jsonp(需要前后端配合)
(1)添加一个<script>
标签,用于发起跨域请求;
jsonp跨域其实也是JavaScript设计模式中的一种代理模式。在html页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的,所以我们可以通过这个“犯罪漏洞”来进行跨域。一般,我们可以动态的创建script标签,再去请求一个带参网址来实现跨域通信
//原生的实现方式
let script = document.createElement('script');
script.src = 'http://www.nealyang.cn/login?username=Nealyang&callback=callback';
document.body.appendChild(script);
function callback(res) {
console.log(res);
(2)jquery的jsonp方式跨域请求:
服务端代码不变,发起ajax请求时配置一个dataType:'jsonp'
,就可以发起一个跨域请求。jsonp指定服务器返回的数据类型为jsonp格式,可以看发起的请求路径,自动带了一个callback=xxx,xxx是jquery随机生成的一个回调函数名称
$.ajax({
url:'http://www.nealyang.cn/login',
type:'GET',
dataType:'jsonp',//请求方式为jsonp
jsonpCallback:'callback',
data:{
"username":"Nealyang"
}
缺点:jsonp方式不支持POST方式跨域请求,就算指定成POST方式,会自动转为GET方式;而后端如果设置成POST方式了,那就请求不了了。
2.CORS
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否。只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。
CORS优缺点:
- CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。
- 优点在于功能更加强大支持各种HTTP Method,缺点是兼容性不如JSONP。
只需要在服务器端做一些小小的改造即可:
// 指定授权访问的域
header("Access-Control-Allow-Origin:*");
// 授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)
header("Access-Control-Allow-Methods:POST,GET");
//在服务器端设置同源策略地址
router.get("/userlist",function(req, res,next){
var user ={name:'Mr.Cao', gender:'male', career:'IT Education'};
res.writeHeader(200,{"Access-Control-Allow-Origin":'http://localhost:63342'});
res.write(JSON.stringify(user));
res.end();
});
3.websocket
Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。
原生WebSocket API使用起来不太方便,我们使用 socket.io它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
//--------------------------------------前端代码:------------------------------
<div>user input:<input type="text">
</div><script src="./socket.io.js"></script>
<script>
var socket =io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function(){
// 监听服务端消息
socket.on('message',function(msg){
console.log('data from server: ---> '+ msg);
});
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
</script>
//---------------------------------------Nodejs socket后台:---------------------------
var http = require('http');
var socket =require('socket.io');
// 启http服务
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-type': 'text/html'});
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg);
console.log('data from client: ---> ' + msg);
});
// 断开处理
client.on('disconnect', function() {
console.log('Client socket has closed.');
});
});
4.postMessage
如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和 window.open方法打开的窗口,它们与父窗口无法通信。HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个 window.postMessage 方法,允许跨窗口通信,不论这两个窗口是否同源。
postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为 *,表示不限制域名,向所有窗口发送。
接下来我们看个例子: http://localhost:63342/index.html 页面向 http://localhost:3000/message.html 传递“跨域请求信息”
//发送信息页面 http://localhost:63342/index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>跨域请求</title>
</head>
<body>
<iframe src="http://localhost:3000/users/reg" id="frm"></iframe>
<input type="button" value="OK" onclick="run()">
</body>
</html>
<script>
function run(){
var frm=document.getElementById("frm");
frm.contentWindow.postMessage("跨域请求信息","http://localhost:3000");
}
</script>
//接收信息页面 http://localhost:3000/message.html
window.addEventListener("message",function(e){
//通过监听message事件,可以监听对方发送的消息。
console.log(e.data);
},false);
5.Vue.js实现
this.$http.jsonp(http://www.an.com:80/login’,{
params:{},
jsonp:’Back’
}).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('80');
console.log('Server is running at port 80...');
6.document.domain+iframe跨域
此方案仅限主域相同,子域不同的跨域应用场景。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1、父窗口:(http://www.an.com/aa.html)
<iframe id=”iframe” src=”http://child.an.com/b.html”></iframe>
<script>
document.domain = ‘an.com’;
var user = ‘admin’;
</script>
2、子窗口:(http://child.an.com/b.html)
<script>
Document.domain = ‘an.com’;
//获取父窗口变量
Alert(‘get js data from parent----->’+window.parent.user);
</script>
7.Location.hash+iframe
1、a.html:(http://www.an1.com/a.html)
<iframe id=’iframe’ src=’http://www.an2.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 back(res){
Alert(‘data from c.html--->’ + res);
}
</script>
2、b.html:(http://www.an2.com/b.html)
<iframe id=”iframe” src=”http://www.an1.com/c.html’” style=”display:none”></ifeame>
<script>
var iframe = document.getElementById(“iframe”);
//监听a.html传来的hash值,再传给c.html
Window.onhashchange = function(){
Iframe.src=iframe.src+location.hash;
};
</script>
3、c.html:(http://www.an1.com/c.html)
<script>
//监听b.html传来的hash值
Window.onhashchange = function(){
//再通过操作同域a.html的js回调,将结果传回
window.parent.parent.Back(“hello:”+location.hash.replace(“#user=”,”));
};
</script>
8.Window.name+iframe跨域
总结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。
1.)a.html:(http://www.an1.com/a.html)
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
// 加载跨域页面
iframe.src = url;
// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第2次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame();
} else if (state === 0) {
// 第1次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.an1.com/proxy.html';
state = 1;
}
};
document.body.appendChild(iframe);
// 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
};
// 请求跨域b页面数据
proxy('http://www.an2.com/b.html', function(data){
alert(data);
});
2.)proxy.html:(http://www.an1.com/proxy….
中间代理页,与a.html同域,内容为空即可。
3.)b.html:(http://www.an2.com/b.html)
<script>
window.name = 'This is an2 data!';
</script>
更多:
https://blog.csdn.net/superaistar/article/details/83618689
https://blog.csdn.net/Jack_zengzhen/article/details/78895205
https://blog.csdn.net/qq_34125349/article/details/79720422
https://blog.csdn.net/liangjielaoshi/article/details/83786388
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html