如何将需要授权的独立Google Apps脚本网络应用嵌入新的Google协作平台?

时间:2019-06-11 23:00:57

标签: google-apps-script x-frame-options google-sites-2016

我创建了一个独立的Google Apps脚本网络应用程序,试图将其嵌入到新的Google网站中。当我登录到用于创建Apps Script项目的帐户时,它可以正常工作。但是,如果我登录了尚未授权该Web应用程序的另一个帐户,则会加载Google Sites页面,但是带有嵌入式Apps Script项目的iFrame无法正确加载。

相反,iFrame会显示“ accounts.google.com拒绝连接”,而控制台会显示“拒绝在框架中显示'https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Fscript.google.com%2Fmacros%2Fs%2FAKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM%2Fexec&followup=https%3A%2F%2Fscript.google.com%2Fmacros%2Fs%2FAKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM%2Fexec”,因为它会将“ X-Frame-Options”设置为“ deny”。 “

据我了解,新用户未获得我的Apps Script Web App的授权,这会触发授权流程。但是,当授权流程通过加载Google登录页面(从上方https://accounts.google.com/ServiceLogin?...)开始时,由于登录页面的X-Frame-Options标头设置为“拒绝”,因此授权流程中断。

我确实尝试了HTMLoutput.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)(请参阅https://developers.google.com/apps-script/reference/html/html-output#setxframeoptionsmodemode),但是我很确定导致Google Sites iFrame错误加载的问题不是我的应用,而是Google的标志在页面上。

链接到Google Site: https://sites.google.com/view/create-user-filter-views/home

链接到Apps Script Web App: https://script.google.com/macros/s/AKfycbzizTNkflSXZbKSF8TTxTR5QoF4LAhPPuSq-1juFdIOdL_IlFM/exec

Google提供的有关如何在新站点中嵌入Apps脚本的文档: https://developers.google.com/apps-script/guides/web#embedding_a_web_app_in_new_sites

如何授权新用户从Google协作平台访问我的网络应用?

我是否需要先将它们定向到我已发布的应用程序脚本网站以进行授权流程,然后将它们定向返回我的Google网站(显然,这是一个糟糕的选择)?

1 个答案:

答案 0 :(得分:1)

首先,您的分析正确。 Google的登录页面(实际上是Google托管内容的很大一部分)的X-Frame-Options设置为拒绝,并且由于该设置,重定向被阻止在iframe中加载。如果用户已经登录到Google,但尚未授权该应用程序,则我相信大多数情况下,他们应该会在iframe中看到授权对话框,而不会出现错误(Alan Wells正在举报)。但是,我并未进行全面测试,它可能适用于同时进行多次登录(例如登录到多个Gmail)的用户,它将把您踢出登录页面并触发X-Frame-Options块。

无论哪种方式,经过一番挖掘,我都找到了一个可行的解决方案。这有点麻烦,因为Apps Script对可使用的内容施加了各种限制。例如,我首先想使用postMessage将消息从嵌入式iframe传递到父页面,如果父在X#秒内未收到消息,则假定iframe加载失败并会将用户重定向到登录/授权应用。 las,postMessage在Apps脚本中不能很好地发挥作用,因为它们是如何嵌入iframe的。

解决方案:

我正在工作的解决方案是使用JSONP方法。 Google here简要提到了这一点。首先,在iframe上放置一个叠加层,以提示用户对应用程序进行身份验证,并附上链接。然后,您两次加载应用程序脚本,一次是作为iframe,另一次是作为<script></script>标签。如果<script>标签成功加载,它将调用一个回调函数,该函数将隐藏提示叠加层,以便使下面的iframe可见。

这是我的代码,经过精简后可以看到它的工作原理:

嵌入式HTML:

<style>
.appsWidgetWrapper {
    position: fixed;
}
.appsWidget {
    width: 100%;
    height: 100%;
    border: none !important;
}
.loggedOut {
    top: 0px;
    left: 0px;
    position: absolute;
    width: 100%;
    height: 100%;
    background-color: darksalmon;
    text-align: center;
}
</style>

<!-- Script loaded as iframe widget with fallback -->
<div class="appsWidgetWrapper">
    <iframe class="appsWidget" src="https://script.google.com/macros/s/SCRIPT_ID/exec?embedIframe"></iframe>
    <div class="loggedOut">
        <div class="loggedOutContent">
            <div class="loggedOutText">You need to "authorize" this widget.</div>
            <button class="authButton">Log In / Authorize</button>
        </div>
    </div>
</div>

<!-- Define JSONP callback and authbutton redirect-->
<script>
    function authSuccess(email){
        console.log(email);
        // Hide auth prompt overlay
        document.querySelector('.loggedOut').style.display = 'none';
    }
    document.querySelectorAll('.authButton').forEach(function(elem){
        elem.addEventListener('click',function(evt){
            var currentUrl = document.location.href;
            var authPage = 'https://script.google.com/macros/s/SCRIPT_ID/exec?auth=true&redirect=' + encodeURIComponent(currentUrl);
            window.open(authPage,'_blank');
        });
    });
</script>

<!-- Fetch script as JSONP with callback -->
<script src="https://script.google.com/macros/s/SCRIPT_ID/exec?jsonpCallback=authSuccess"></script>

和Code.gs(应用脚本)

function doGet(e) {
    var email = Session.getActiveUser().getEmail();

    if (e.queryString && 'jsonpCallback' in e.parameter){
        // JSONP callback
        // Get the string name of the callback function
        var cbFnName = e.parameter['jsonpCallback'];
        // Prepare stringified JS that will get evaluated when called from <script></script> tag
        var scriptText = "window." + cbFnName + "('" + email + "');";
        // Return proper MIME type for JS
        return ContentService.createTextOutput(scriptText).setMimeType(ContentService.MimeType.JAVASCRIPT);
    }

    else if (e.queryString && ('auth' in e.parameter || 'redirect' in e.parameter)){
        // Script was opened in order to auth in new tab
        var rawHtml = '<p>You have successfully authorized the widget. You can now close this tab and refresh the page you were previously on.</p>';
        if ('redirect' in e.parameter){
            rawHtml += '<br/><a href="' + e.parameter['redirect'] + '">Previous Page</a>';
        }
        return HtmlService.createHtmlOutput(rawHtml);
    }
    else {
        // Display HTML in iframe
        var rawHtml = "<h1>App Script successfully loaded in iframe!</h1>"
            + "\n"
            + "<h2>User's email used to authorize: <?= authedEmail ?></h2>";
        var template = HtmlService.createTemplate(rawHtml);
        template.authedEmail = email;
        return template.evaluate().setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
    }
}

在此示例中,“ authSuccess”是我的JSONP回调函数,如果脚本成功,则应使用授权用户的电子邮件进行调用。否则,如果用户需要登录或授权,则它将不会,并且重叠式广告将保持可见状态,并阻止iframe错误向用户显示。

我还发布了完整代码on Github,其中的结构可能更容易看到。