Javascript IFRAME沙箱中的Google OAuth2和应用程序脚本

Javascript IFRAME沙箱中的Google OAuth2和应用程序脚本,javascript,iframe,google-apps-script,oauth-2.0,Javascript,Iframe,Google Apps Script,Oauth 2.0,就web开发而言,我是一个新手,对于Google应用程序脚本和OAuth2.0更是如此。话虽如此,我已经做了足够的研究,也尝试了一些技巧,但仍然无法克服这个问题 我从这里借用了样本: 然后创建了一个应用程序脚本项目,其中包含一个index.html文件,其中包含来自该页面的代码。我还在开发人员控制台上创建了一个项目,创建了一个客户机ID、API密钥并打开了所需的API支持。我还对示例进行了必要的更改,以反映新的客户端ID和API密钥 index.html页面由html服务提供,SandBox模

就web开发而言,我是一个新手,对于Google应用程序脚本和OAuth2.0更是如此。话虽如此,我已经做了足够的研究,也尝试了一些技巧,但仍然无法克服这个问题

我从这里借用了样本:

然后创建了一个应用程序脚本项目,其中包含一个index.html文件,其中包含来自该页面的代码。我还在开发人员控制台上创建了一个项目,创建了一个客户机ID、API密钥并打开了所需的API支持。我还对示例进行了必要的更改,以反映新的客户端ID和API密钥

index.html页面由html服务提供,
SandBox模式
设置为IFRAME。如果我在浏览器窗口中加载URL(比如使用匿名模式)并单击“授权”按钮,它将打开Google登录窗口。但在登录后,它会打开两个包含消息的新选项卡

请关上这个窗户

并且原始浏览器窗口不显示任何更改

JavaScript控制台显示如下错误消息:

不安全的JavaScript试图为URL为“”的框架启动导航 从带有URL的框架
https://accounts.google.com/o/oauth2/postmessageRelay?parent=https%3A%2F%2F…6lxdpyio6iqy script.googleusercontent.com#rpctoken=288384029&forcesecure=1
。 尝试导航的框架是沙盒,因此 不允许它的祖先航行

从消息来看,这似乎是使用IFRAME的一种效果,某种安全特性阻止回调传递到原始窗口。如果我重新加载原始窗口,一切正常。但这不是我理想中想要的

我如何解决这个问题?这是一个非常简单的项目,如果有帮助的话,我可以提供源代码

谢谢, 帕万

编辑:这是我正在尝试的示例代码。您需要拥有客户端ID和API密钥,并在Google控制台中设置JS源代码,才能正常工作:

代码.gs

function doGet(e) {
    return HtmlService.createHtmlOutputFromFile('index').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
index.html

<!--
  Copyright (c) 2011 Google Inc.

  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  use this file except in compliance with the License. You may obtain a copy of
  the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  License for the specific language governing permissions and limitations under
  the License.

  To run this sample, replace YOUR API KEY with your application's API key.
  It can be found at https://code.google.com/apis/console/?api=plus under API Access.
  Activate the Google+ service at https://code.google.com/apis/console/ under Services
-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8' />
  </head>
  <body>
    <!--Add a button for the user to click to initiate auth sequence -->
    <button id="authorize-button" style="visibility: hidden">Authorize</button>
    <script type="text/javascript">
      // Enter a client ID for a web application from the Google Developer Console.
      // The provided clientId will only work if the sample is run directly from
      // https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
      // In your Developer Console project, add a JavaScript origin that corresponds to the domain
      // where you will be running the script.
      var clientId = 'YOUR_CLIENT_ID';


      // Enter the API key from the Google Develoepr Console - to handle any unauthenticated
      // requests in the code.
      // The provided key works for this sample only when run from
      // https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
      // To use in your own application, replace this API key with your own.
      var apiKey = 'YOUR API KEY';


      // To enter one or more authentication scopes, refer to the documentation for the API.
      var scopes = 'https://www.googleapis.com/auth/plus.me';

      // Use a button to handle authentication the first time.
      function handleClientLoad() {
        gapi.client.setApiKey(apiKey);
        window.setTimeout(checkAuth,1);
      }

      function checkAuth() {
        gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true, response_type: 'token'}, handleAuthResult);
      }


      function handleAuthResult(authResult) {
        var authorizeButton = document.getElementById('authorize-button');
        if (authResult && !authResult.error) {
          authorizeButton.style.visibility = 'hidden';
          makeApiCall();
        } else {
          authorizeButton.style.visibility = '';
          authorizeButton.onclick = handleAuthClick;
        }
      }

      function handleAuthClick(event) {
        gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false, response_type: 'token'}, handleAuthResult);
        return false;
      }

      // Load the API and make an API call.  Display the results on the screen.
      function makeApiCall() {
        gapi.client.load('plus', 'v1', function() {
          var request = gapi.client.plus.people.get({
            'userId': 'me'
          });
          request.execute(function(resp) {
            var heading = document.createElement('h4');
            var image = document.createElement('img');
            image.src = resp.image.url;
            heading.appendChild(image);
            heading.appendChild(document.createTextNode(resp.displayName));
            heading.appendChild(document.createTextNode(resp.emails[0].value));

            document.getElementById('content').appendChild(heading);
          });
        });
      }
    </script>
    <script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
    <div id="content"></div>
    <p>Retrieves your profile name using the Google Plus API.</p>
  </body>
</html>

授权
//从Google开发者控制台输入web应用程序的客户端ID。
//提供的clientId仅在直接从运行示例时有效
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
//在开发人员控制台项目中,添加对应于域的JavaScript源代码
//您将在其中运行脚本。
var clientId='您的客户ID';
//从Google DevelopePR控制台输入API密钥-以处理任何未经验证的
//代码中的请求。
//提供的密钥仅在从运行时适用于此示例
// https://google-api-javascript-client.googlecode.com/hg/samples/authSample.html
//要在您自己的应用程序中使用,请用您自己的API密钥替换此API密钥。
var apiKey='您的API密钥';
//要输入一个或多个身份验证作用域,请参阅API的文档。
var作用域https://www.googleapis.com/auth/plus.me';
//第一次使用按钮处理身份验证。
函数handleClientLoad(){
gapi.client.setApiKey(apiKey);
setTimeout(checkAuth,1);
}
函数checkAuth(){
auth.authorize({client\u id:clientId,scope:scopes,immediate:true,response\u type:'token'},handleAuthResult);
}
函数handleAuthResult(authResult){
var authorizeButton=document.getElementById('authorized-button');
if(authResult&!authResult.error){
authorizeButton.style.visibility='hidden';
makeApiCall();
}否则{
authorizeButton.style.visibility='';
authorizeButton.onclick=handleAuthClick;
}
}
函数handleAuthClick(事件){
auth.authorize({client_id:clientId,scope:scopes,immediate:false,response_type:'token'},handleAuthResult);
返回false;
}
//加载API并进行API调用。在屏幕上显示结果。
函数makeApiCall(){
load('plus','v1',function(){
var request=gapi.client.plus.people.get({
“userId”:“我”
});
请求执行(功能(resp){
var heading=document.createElement('h4');
var image=document.createElement('img');
image.src=resp.image.url;
标题.附加子项(图像);
heading.appendChild(document.createTextNode(resp.displayName));
heading.appendChild(document.createTextNode(resp.emails[0].value));
document.getElementById('content').appendChild(标题);
});
});
}
使用Google Plus API检索您的配置文件名


找到了一个解决方案。。。不太好,但很管用:

诀窍是在auth窗口关闭之前删除oauth2relay iFrame。关闭窗口后,您必须再次添加帧,并立即执行请求,如果该请求有效,则由授权应用程序的用户执行

小心点
此脚本不会检查用户是否同时注销或令牌是否过期,只要webapp窗口打开,则使用相同的令牌

Code.js:

function doGet(e) {
  return HtmlService.createTemplateFromFile('Index').evaluate().setTitle(formSettings.title).setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function include(file) {
  return HtmlService.createHtmlOutputFromFile(file).getContent();
}

function doPost(meta) {
  if (!meta || !meta.auth) {
    throw new Error('not authorized');
    return;
  }
  var auth = JSON.parse(UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + meta.auth.access_token, { muteHttpExceptions: true }).getContentText());
  if (auth.error || !auth.email) {
    throw new Error('not authorized');
    return;
  }

  if (typeof this[meta.method + '_'] == 'function') {
    return this[meta.method + '_'](auth.email, meta.data);
  }
  throw new Error('unknown method');
}

function test_(email, data) {
  return email;
}
Index.html:

<html>
  <head>
    <?!= include('JavaScript'); ?>
  </head>
  <body>
    <div class="content-wrapper">

    </div>
  </body>
</html>

Javascript.html:

<script type='text/javascript' src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type='text/javascript' src="//apis.google.com/js/client.js?onload=apiLoaded" async></script>
<script type='text/javascript'>
    var clientId = '*************-********************************.apps.googleusercontent.com';
    var scopes = ['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email'];

    var loaded = false;
    var auth = null;

    function apiLoaded() {  
      loaded = true;
      login();
    }

    window._open = window.open;
    window._windows = [];
    window.open = function(url) {
      var w = window._open.apply(window,arguments);
      window._windows.push(w);
      return w;
    }

    function login(step) {
      step || (step = 0);
      if (!loaded) {
        return;
      }  
      gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: (step <= 0 || step >= 2) }, function(authResult) {
        if (authResult) {
          if (authResult.error) {
            if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step <= 0) {
              var interval = setInterval(function() {
                var $ifr = $('iframe');//[id^=oauth2relay]');
                if (!window._windows.length) {
                  clearInterval(interval);
                  return;
                }
                if ($ifr.length) {
                  clearInterval(interval);
                  $ifr.detach();
                  var w = window._windows.pop();
                  if (w) {
                    var interval2 = setInterval(function() {
                      if (w.closed) {
                        clearInterval(interval2);
                        $('body').append($ifr);
                        login(2);
                      }
                    });
                  } else {                
                    $('body').append($ifr);
                  }
                }
              },500);
              login(1);
            } else if (authResult.error == 'immediate_failed' && authResult.error_subtype == 'access_denied' && step >= 2) {
              //user canceled auth
            } else {
              //error
            }
          } else {
            auth = authResult;
            doPost('test', { some: 'data' }, 'test');
          }
        } else {
          //error
        }
      });
    }

    function test() {
      console.log(arguments);
    }

    //with this method you can do a post request to webapp server
    function doPost(method, data, callbackName) {
      data || (data = {});
      google.script.run.withSuccessHandler(onSuccess).withFailureHandler(onError).withUserObject({ callback: callbackName }).doPost({ method: method, data: data, auth: auth });
    }

    function onSuccess(data, meta) {
      if (typeof window[meta.callback] == 'function') {
        window[meta.callback](null, data);
      }
    }

    function onError(err, meta) {
      if (typeof window[meta.callback] == 'function') {
        window[meta.callback](err);
      }
    }
</script>

var clientId='***************-******************************************************.apps.googleusercontent.com';
变量作用域=['https://www.googleapis.com/auth/plus.me', 'https://www.googleapis.com/auth/userinfo.email'];
var-load=false;
var-auth=null;
函数apiloadded(){
加载=真;
登录();
}
window.\u open=window.open;
窗口。_窗口=[];
window.open=函数(url){
var w=窗口.\u open.apply(窗口,参数);
窗口。\u窗口。推(w);
返回w;
}
函数登录(步骤){
步骤| |(步骤=0);
如果(!