js之跨域


关于img标签跨域的问题

1、PC端:因为img标签不是js,所以不能在请求图片地址的时候加请求头。
2、移动端:移动端不能记住请求的账号密码,所以移动端必须请求图片的时候发送请求头,这是一个很大问题。

PC端解决方案

解决思路:当请求存在跨域时,会弹出需要身份验证的对话框,输入一次账号密码后,再重新请求,就不会有对话框了,说明浏览器会记住账号密码,所以只要用ajax将账号密码发送请求就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var url = 'http://10.10.2.111:8000/fcrepo/rest/msxAD/3202/201712214.png';  //跨域图片地址
$.ajax({
url: url,
username: 'superadmin',
password: 123456
}).done(function(){
console.log(2) //用来请求是否成功
var img = new Image()
img.src = imgUrl;
img.onload = function(e) {
console.log(1) //用来判断图片是否加载完成
document.getElementsByClassName('img')[0].appendChild(img)
}
})
移动端解决方案

参考地址:链接

解决思路:因为移动端不会弹出对话框,且不能保存账号密码,不能用ajax发送账号密码请求来解决。先将图片的跨域地址请求成功,然后img标签地址不要用跨域地址,而是用window.URL.createObjectURL(xhr.response)进行转换

1
2
3
4
5
6
7
8
9
10
11
12
var url = 'http://10.10.2.111:8000/fcrepo/rest/msxAD/3202/201712214.png';
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.setRequestHeader('Authorization', 'Basic ' + btoa('superadmin:123456')) //这是请求头,这样就不用发送账号密码了,btoa是自定义的转换方法
xhr.onload = function() {
if (xhr.status === 200 || xhr.status === 304) {
console.log(1)
var img = new Image()
img.src = window.URL.createObjectURL(xhr.response) //重点是这条,将url地址转换
document.getElementsByClassName('img')[0].appendChild(img)
}
};

用表单代替ajax

通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

教程:链接

使用场景

当项目使用连连支付,请求连连的接口,报跨域错误,客户端和后端没有跨域问题,H5是有跨域问题,假如用H5请求就需要解决这一问题。这时候就需要用form表单代替ajax

使用方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function postcall( url, params, target){
var tempform = document.createElement("form");
tempform.action = url;
tempform.method = "post";
tempform.style.display="none"
if(target) {
tempform.target = target;
}

for (var x in params) {
var opt = document.createElement("input");
opt.name = x;
opt.value = params[x];
tempform.appendChild(opt);
}

var opt = document.createElement("input");
opt.type = "submit";
tempform.appendChild(opt);
document.body.appendChild(tempform);
tempform.submit();
document.body.removeChild(tempform);
}

一般跨域情况

同源策略和限制

协议、域名和端口只要有一个不同,就是跨域。意思只要3者一致,就是同源。
不同源的限制:1、Cookie、LocalStorage和IndexDB无法获取。2、无法获取和操作DOM。3、不能发送Ajax请求。

跨域通信的几种方式

1、JSONP。2、webSocket。3、CORS。4、Hash。5、postMessage

JSONP

jsonp是json with padding(填充式json或参数式json)的简写。html中script标签可以引入其他域下的js,比如引入线上的jquery库。利用这个特性,可实现跨域访问接口,需要后端支持。其中src的链接会被当成js执行。

步骤 操作
1 在页面上定义数据处理函数show
2 创建script标签,src的地址执行后端接口,最后加个参数callback=show
3 服务端在收到请求后,解析参数,计算返回数据,输出show(data)字符串
4 show(data)会放到script标签作为js执行。此时会调用show函数,将data作为参数
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//页面代码
<!doctype html>
<html>
<head></head>
<body>
<div class="newsWrap">
<ul>
<li>第11日前瞻:中国冲击4金 博尔特再战</li>
<li>男双力争会师决赛</li>
<li>女排将死磕巴西!</li>
</ul>
<div class="btn">
<button>换一组</button>
</div>
</div>

<script>
function $$(str){
return document.querySelector(str);
}
var btn=$$(".newsWrap .btn button");
var ul=$$(".newsWrap ul");
btn.addEventListener("click",function(){ //1、点击页面换一组
var script=document.createElement("script");
var src="http://localhost:8080/getNews?callback=getnew";
script.src=src;
document.head.appendChild(script); //2、向head添加script标签,向传输的地址发送请求,并提供参数callback=getnew
document.head.removeChild(script); //4、请求完成后,删除script标签
},false);
function getnew(str){ //5、页面接受数据getnews("["","",""]")后执行函数,把数据添加到页面上
var html="";
for(var i=0;i<str.length;i++){
html+="<li>"+str[i]+"</li>";
}
ul.innerHTML=html;
}
</script>
</body>
</html>

//后台代码
router.get('/getNews', function(req, res) {
var getnews = req.query.callback; // 通过 req.query获取请求参数
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心",
"没有中国选手和巨星的110米栏 我们还看吗?",
"中英上演奥运金牌大战",
"博彩赔率挺中国夺回第二纽约时报:中国因对手服禁药而丢失的奖牌最多",
"最“出柜”奥运?同性之爱闪耀里约",
"下跪拜谢与洪荒之力一样 都是真情流露"
];
var arr=[],num=[-1],b;
for(var i=0;i<3;i++){
b=Math.floor(Math.random()*(news.length));
for(var j=0;j<num.length;j++){
if(b===num[j]) {i--;}
else {
arr.push(news[b]);
num.push(b); break;
}
};
};
res.send(getnews+"("+JSON.stringify(arr)+")"); //3、发送的数据为getnews("["","",""]");
});
webSocket

解决问题:HTTP 协议有一个缺陷:通信只能由客户端发起。webSocket的特点:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息

Hash

url的#后面的内容就是Hash,Hash改变,页面不会刷新。

1
2
3
4
5
6
7
8
9
10
// 举例
// A页面通过iframe嵌套了B页面,A页面给B发送消息
A页面:
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'jsonString';

B页面:
window.onhashchange = function () { //通过onhashchange方法监听,url中的 hash 是否发生变化
var data = window.location.hash;
};

CORS

CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。
服务端设置Access-Control-Allow-Origin就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
当你使用Ajax发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,跟服务端的Access-Control-Allow-Origin设置进行匹配,匹配成功则在返回结果中加入一个响应头:Access-Control-Allow-Origin;

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//假设这个页面的域名为http://a.jrg.com
<!doctype html>
<html>
<head></head>
<body>
<div class="newsWrap">
<ul>
<li>第11日前瞻:中国冲击4金 博尔特再战</li>
<li>男双力争会师决赛</li>
<li>女排将死磕巴西!</li>
</ul>
<div class="btn">
<button>换一组</button>
</div>
</div>

<script>
//CORS跨域获取数据
function $$(str){
return document.querySelector(str);
}
var btn=$$(".newsWrap .btn button");
var ul=$$(".newsWrap ul");
btn.addEventListener("click",function(){
var xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status===200|| xhr.status===304){
getnew(JSON.parse(xhr.responseText));
}
else console.log("出错了");
}
}
xhr.open("get","http://b.jrg.com:8080/getNews",true); //向域名为http://b.jrg.com:8080请求数据
xhr.send();
},false);
</script>
</body>
</html>

router.get("/getNews",function(reg,res){
var getnews=reg.query.callback;
var news = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心",
"没有中国选手和巨星的110米栏 我们还看吗?",
"中英上演奥运金牌大战",
"博彩赔率挺中国夺回第二纽约时报:中国因对手服禁药而丢失的奖牌最多",
"最“出柜”奥运?同性之爱闪耀里约",
"下跪拜谢与洪荒之力一样 都是真情流露"
];
var arr=[],num=[-1],b;
for(var i=0;i<3;i++){
b=Math.floor(Math.random()*(news.length));
for(var j=0;j<num.length;j++){
if(b===num[j]) {i--;}
else {
arr.push(news[b]);
num.push(b); break;
}
};
};
res.header("Access-Control-Allow-Origin","http://a.jrg.com:8080"); //表示接受http://a.jrg.com:8080的请求
res.send(arr);
});
PostMessage

window.postMessage()通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息。
window.postMessage(message, targetOrigin)发送数据,otherwindow.addEventListener(‘message’, () => {})接收数据

1、otherWindow
其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames。
2、message
要传递的数据。使用JSON.stringify()方法对对象参数序列化.
3、targetOrigin
向哪个地址发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
//URL: http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input',function(){
console.log(this.value);
window.frames[0].postmessage(this.value,"http://b.jrg.com:8080/b.html"); //向b.jrg.com:8080/b.html发送数据
});
</script>

<script>
// URL: http://b.jrg.com:8080/b.html
window.addEventListener('message',function(e){ //监听事件,当事件发生时接收数据
$('input') = e.data;
console.log(e.data);
});
</script>