研究vue-canvas-poster时,图片下载解决的跨域问题,在此将对其的理解做下汇总
一、跨域
1、跨域定义
广义:
第一:资源跳转:各种页面跳转。如:链接跳转;路由重定向;提交表单;
第二:资源嵌入:文件的引入。如:link、script、img、frame,另外样式中背景图片以及字体的引入background:url()、@font-face()
第三:脚本请求:各种的接口请求。js发起的ajax请求
狭义:
浏览器不能执行其他网站的脚本,浏览器同源策略引起的。同源:域名,协议,端口一致。
限制:Cookie、LocalStorage和IndexDB无法读取;DOM和Js对象无法获得;AJAX请求不能发送。
栗子:http【协议】://blog【子域名】.test【主域名】.com:8080【端口号】/
2、解决方案
方案一:document.domain + iframe跨域(仅适用于主域相同,子域不同)
实现原理:通过document.domain来设置基础主域,浏览器通过查看它来判断是否为同域,共享cookie。
// 父窗口:(http://www.domain.com/a.html); 子窗口:(http://child.domain.com/b.html)
// 父窗口代码
<script>
document.domain = 'domain.com';
var user = 'admin';
</script>
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
// 子窗口代码
<script>
document.domain = 'domain.com';
alert(window.parent.user); // 获取父窗口中变量
</script>
方案二:location.hash + iframe跨域
实现原理:A域与B域间跨域相互通信,通过中间页C来实现,三个页面,不同域利用iframe的location.hash传值,相同域间直接js访问通信。
栗子描述:A与B不同域,B与C不同域,而A与C同域;不同域间利用hash值单向通信,同域间通过parent.parent访问
// A域,a.html http://www.domainA.com/a.html
<iframe id="iframe" src="http://www.domainB.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
setTimeout(function() {
iframe.src = iframe.src + '#user=admin'; // 向b.html传hash值
}, 1000);
function onCallback(res) {
alert('data from c.html ---> ' + res); // 开放给同域c.html的回调
}
</script>
// B域,b.html http://www.domainB.com/b.html
<iframe id="iframe" src="http://www.domainA.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
window.onhashchange = function () {
iframe.src = iframe.src + location.hash
}
</script>
// A域,c.html http://www.domainA.com/c.html
<script>
// 监听的是b.html传过来的hash值
window.onhashchange = function () {
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', '')) // 操作a.html的回调,将结果传回
}
</script>
方案三、window.name + iframe跨域
实现原理:利用window的name值,在不同页面或者说不同域下加载后仍然存在,并且可以支持很长的name(2MB)
1、a.html http://www.domain1.com/a.html
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement('iframe');
iframe.src = url; // 加载跨域页面
// onload触发两次,第一次加载跨域页面,并留存数据于window.name
iframe.onload = function() {
if (state === 1) {
// 第二次onload(同域proxy页)成功后,读取同域window.name中数据
callback(iframe.contentWindow.name);
destoryFrame()
} else if (state === 0) {
// 第一次onload(跨域页)成功后,切换到同域代理页面
iframe.contentWindow.location = 'http://www.domain1.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.domain2.com/b.html',function(data){
alert(data)
})
2、proxy.html http://www.domain1.com/proxy.html
中间代理页,与a.html同域,内容为空即可
3、 b.html http://www.domain2.com/b.html
<script>
window.name = 'This is domain2 data!';
</script>
小结:通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。巧妙地绕过了浏览器的跨域访问限制。
方案四、postMessage跨域
介绍:postMessageAPI是为数不多可以跨域操作的window属性之一。
解决的问题:
1、页面和其打开的新窗口的数据传递;
2、多窗口之间消息传递;
3、页面与嵌套的iframe消息传递;
4、上面三个场景的跨域数据传递。
使用参数:postMessage(data,origin)
data:html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin:协议 + 主机 + 端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话为"/"。
1、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');
iframe.onload = function() {
var data = {name: 'aym'}; // 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
}
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
2、b.html http://www.domain2.com/b.html
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
方案五、跨域资源共享(CORS)
普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置;若要带cookie请求:前后端都需要设置。
注意点:同源策略限制,所读取的cookie为跨域请求接口,不属于当前页。
一、前置设置:
1、原生的ajax
var xhr = new XMLHttpRequest();
xhr.withCredentials = true; // 前端设置是否带cookie
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);
}
}
2、jQuery ajax
$.ajax({
...
xhrFields: { withCredentials: true }, // 前端设置是否带cookie
crossDomain: true, // 会让请求头中包含跨域的额外信息,但不会含cookie
...
});
3、vue框架
axios设置:axios.defaults.withCredentials = true
vue-resource设置:Vue.http.options.credentials = true
二、服务端设置:
CORS需要前后端配合,当后端设置成功之后,前端浏览器控制台则不会出现跨域报错信息,反之,就是未成功。
1、Java后台
/*
* 导入包:import javax.servlet.http.HttpServletResponse;
* 接口参数中定义:HttpServletResponse response
*/
// 允许跨域访问的域名:若有端口需写全(协议+域名+端口),若没有端口末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");
// 允许前端带认证cookie:启用此项后,上面的域名不能为'*',必须指定具体的域名,否则浏览器会提示
response.setHeader("Access-Control-Allow-Credentials", "true");
// 提示OPTIONS预检时,后端需要设置的两个常用自定义头
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
2、Node.js后台
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...');
方案六、nginx代理跨域
1、nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置
location / {
add_header Access-Control-Allow-Origin *;
}
2、nginx反向代理接口跨域
跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。