使用iframe将帆软报表集成在Vue大屏中,过程中遇到好几个坑,特此记录。
请求帆软获取token的接口拿到token,存到cookie中
携带cookie请求帆软的报表页面。将其以iframe的形式嵌入vue报表中
这里遇到了第一个问题,请求接口的时候 http get请求报错403,浏览器报错
Access to XMLHttpRequest at
'http://帆软服务器ip/getToken' from origin 'http://代理服务器ip'
has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
是浏览器的同源策略引起的跨域问题。
经过查阅资料,生产环境解决跨域问题的办法有很多JSONP,CORS,反向代理等。
这里我采用的是反向代理。
只需要在代理服务器那里做个反向代理即可。项目部署架构如下:
一开始我以为使用的Nginx做的代理,同事反馈说是使用的Apache做的代理,两段设置都贴上吧:
Nginx:(没测试过)
location ^~ /getToken{
proxy_pass http://帆软服务器ip/getToken;
}
Apache:
ProxyRequests Off
ProxyPass /getToken http://帆软服务器ip/getToken
ProxyPassReverse /getToken http://帆软服务器ip/getToken
注意: 这里我犯了个低级错误。设置了代理以后,那么发Axios请求的时候,就应该是请求服务器的ip,而不是直接请求帆软的ip,即:
utils.ajax.get("http://帆软服务器ip/getToken", {}) // 错误
utils.ajax.get("http://代理服务器ip/getToken", {}) // 正确
这里无需多说
setCookie(cname, cvalue, exhours) {
let d = new Date();
d.setTime(d.getTime() + (exhours * 60 * 60 * 1000));
let expires = "expires=" + d.toUTCString();
document.cookie = cname + "=" + cvalue + "; " + expires;
},
getCookie(cname) {
let name = cname + "=";
let ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(name) === 0)
return c.substring(name.length, c.length);
}
return null;
},
removeCookie(cname) {
this.setCookie(cname, '', -1);
}
首先仍然是跨域问题,当iframe中的地址跟地址栏中的地址存在跨域时,请求将不能携带cookie。具体表现为:即使我拿到了token存在cookie中,请求报表页面时还会被拦截到登陆注册页,而且输入账号密码也无法登陆,无限循环登陆注册页。
解决仍然采用Apache的反向代理:
ProxyPass /fineBI/decision http://帆软服务器ip:port/fineBI/decision
解决了跨域问题之后,虽然iframe的src携带了cookie,但仍然无法登陆,经过跟帆软的沟通,他们还需要额外在请求头提供Authorization: Bearer token
来进行身份校验(我nm…)
iframe src是无法携带请求头的,只能另辟蹊径了。
这里参考了这位大大的文章: VUE+iframe添加请求头
<iframe id="iframe" src="" />
setTimeout(() => {
var iframe = document.querySelector("#iframe");
this.populateIframe(iframe, [["Authorization", "Bearer " + getToken()]]);
}, 0);
methods: {
populateIframe(iframe, headers) {
var xhr = new XMLHttpRequest();
xhr.open("GET", 'http:localhost:8080/xxx');
xhr.responseType = "blob";
headers.forEach((header) => {
xhr.setRequestHeader(header[0], header[1]);
});
xhr.onreadystatechange = () => {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200) {
iframe.src = URL.createObjectURL(xhr.response);
}
}
};
xhr.send();
},
}
这样改写完成以后,确实可以请求网址得到帆软的页面元素,但是仍然有两个问题:
经过帆软前辈的提醒,考虑到是不是
xhr.responseType = "blob"
这一行的问题,遂进行修改,使用xhr.responseType = "text"
修改后的方法:
populateIframe(iframe, headers) {
var xhr = new XMLHttpRequest();
xhr.open("GET", 'http:localhost:8080/xxx');
xhr.responseType = "text";
headers.forEach((header) => {
xhr.setRequestHeader(header[0], header[1]);
});
xhr.onreadystatechange = () => {
if (xhr.readyState === xhr.DONE) {
if (xhr.status === 200) {
// iframe.src = URL.createObjectURL(xhr.response);
let iframe = document.getElementById('frame-box');
iframe = iframe.contentWindow || ( iframe.contentDocument.document || iframe.contentDocument);
iframe.document.open();
iframe.document.write(xhr.response);
iframe.document.close();
}
}
};
xhr.send();
},
修改之后成功把页面展示了出来。
就在我十分欣喜准备写篇博客庆祝一下的时候,噩耗又来了。页面是可以展示了,但是不能使用报表页面上的导出和打印功能,我他喵的。得,继续看吧。
根据我英语六级的水平判断,报表在打印之前,会发一个请求来检查是否登陆。
肯定是这个请求导致它判断我没有登陆。我又让同事分别在我们嵌入的报表页面和在浏览器上的报表页面点击导出来判断头文件还存在哪些不同,经过对比,我发现
我从帆软接口请求到的token, 只附带到了header Authorization中,并没有附带到cookie中,而我本地测试的时候确实是附带到了cookie中。这令我百思不得其解。
经过详细的对比,把问题锁定在cookie的path上:
本地:
服务器:
经过打包,服务器上的path,变为了/myprojectname
。cookie中的path是cookie生效的范围。path不同,自然没法附带这个cookie。
所以我们修改上面提到的getCookie函数:
document.cookie = cname + "=" + cvalue + "; " + expires + '; path=/;'
至此,打印、导出也可以正常使用。
梳理一下认证过程,之前页面单点登录的时候,cookie未附带也登录成功,说明登录的时候只验证了Authorization
,而打印、导出的时候又只验证cookie
,不得不感叹帆软的验证方式有点儿奇怪。
左手是过目不忘的萤火,右手是十年一个漫长的打坐。