Iframe应用:嵌入浅出

587 阅读4分钟

cat.jpg

背景

应用服务平台集成监控运维平台的监控看板,监控运维平台按照应用服务场景构建看板,使用Iframe嵌入应用服务平台。

image.png

基础环境

轻松开篇

// 引入iframe标签
<iframe id="monitor-iframe" class="grafana-wrap-frame" allow="fullscreen" :src="url" frameborder="0"></iframe>

// 设置url
url: 'http://paas.blueking.com/monitorv3/grafana/d/QgFGIJqIz/fitosyun-zhu-ji?var-object_id=123'

白屏问题

大大的白屏,受限于安全限制,https协议下不允许嵌入http协议的页面。

13E1F3E4.png

Mixed Content: xxx This request has been blocked; the content must be served over HTTPS.

如何解决呢?按照证书方式升级httphttps显然动作太大,不现实。尝试在升级页面的head中加入meta自动将http的不安全请求升级为https。

<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

再次尝试还是无效果。

白屏场景2

另一种白屏场景是子页面系统不允许以iframe方式内嵌。在nginx中配置X-Frame-OptionsContent-Security-Policy "frame-ancestors"来开放可被嵌入的域名。

5b7f496d781e672a3457e272cb4b267.jpg

最终决定使用同源策略来规避这些安全限制。

跨域问题

跨域(Cross-Origin)指的是浏览器阻止前端网页从一个域名(Origin)向另一个域名的服务器发送请求。具体来说,一个页面的协议、域名、端口三者任意一个与请求的目标地址不同,就被视为跨域请求。

同源方案

同源策略的定义:如果两个URL的协议、域名和端口都相同,则这两个URL具有相同的源。

应用方案即用应用服务平台的 [paas.server.com] 做层代理来访问监控运维平台的监控看板。如下去掉域名前缀,默认将使用应用服务平台的域名访问。这样打破了所有跨域带来的安全限制。

// 设置url
url: '/monitorv3/grafana/d/QgFGIJqIz/fitosyun-zhu-ji?var-object_id=123'

//配置nginx
location /monitorv3/ {
    proxy_pass http://paas.blueking.com/monitorv3/;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 600s;
}

认证打通

嵌入是成功了,但是认证体系是两套,嵌入后内部页面会重定向到监控运维平台的登录页面。需要模拟登录来完成认证打通。iframe中设置Cookies也依赖于同源策略

  • 在应用服务中模拟调用监控运维的登录api,拿到token
  • 将token按照监控运维的规范设置进Cookies
import Cookies from 'js-cookie';

getBkToken().then(res => {
    Cookies.set('bk_token', res);
    this.isLogin = true;
}).catch((err) => {
    // todo
});

Token过期问题

当然还有token过期问题,两个系统的过期不同步,可能存在父平台还未过期,子系统过期场景,不过问题不大,拉长过期时间可抵消出现频率。

1438A7A2.png

双滚动条问题

父页面和iframe中子页面同时存在纵向滚动条,显示交互存在瑕疵。出现这种问题的原因是子页面构建完成高度能撑多高并不确定,父页面起始设置的高度不能确定导致出现Y轴滚动条。解决思路也就两种:

  1. 保留父页面滚动条,此方案使用所用场景,把交互方式控制在自己手里
  2. 保留子页面滚动条,此方案适合界面相对简单纯净的场景

方案一

分同源和不同源场景,不同源情况下可以使用postMessage api 建立双向通讯,在子页面渲染完成后把最终高度告知父页面并动态设置给父容器,消除子页面中滚动条。同源场景相对简单,采用postMessage通讯方式,或者直接获取子页面iframe中DOM 对象的高度设置。当然也可以不通讯,适当延迟动态获取高度动态设置也可行。

    const iframe = document.getElementById('monitor-iframe');
    if (iframe.contentWindow || iframe.contentDocument) {
      const doc = iframe.contentDocument || iframe.contentWindow.document;
      const elem = doc.querySelector('.react-grid-layout');
      if (elem) {
        setIframeHeight(elem.offsetHeight + 32);
        setLoading(false);
      }
    } else {
      setLoading(false);
      console.log('Cannot access iframe contentWindow or contentDocument');
    }

方案二

设置样式,保证父容器不产生Y轴滚动条即可。此方案适合界面相对简单纯净的场景,一些局部内嵌 + 其它业务布局组合的场景,高度溢出父容器避免不了Y轴滚动条场景。

后记

Iframe应用最关键的因素就是同源,突破浏览器安全限制。其次就是把显示、交互、通讯控制在自己手里,才能优雅内嵌。