Google 选择器身份验证弹出窗口被阻止

Posted

技术标签:

【中文标题】Google 选择器身份验证弹出窗口被阻止【英文标题】:Google picker auth popup is being blocked 【发布时间】:2015-07-19 11:40:08 【问题描述】:

我有一个列出工作的移动网站,用户申请并上传他们的简历(简历) - 我希望他们能够从他们的 Google Drive 中选择一个文件。

我在这里创建了 Hello world 示例 - https://developers.google.com/picker/docs/(为方便起见,此处复制代码)

问题是,如果尚未登录云端硬盘,则会启动一个登录弹出窗口。这在台式机上已经够糟糕了,但在手机上真的很糟糕。

我试过this solution,但得到'TypeError: gapi.auth is undefined'

我还尝试从 onclick 事件而不是文档中描述的 onload 启动选择器。

function launchDrive()
    
        gapi.load('auth', 'callback': onAuthApiLoad);
        gapi.load('picker', 'callback': onPickerApiLoad);
    
<input type='button' value='Launch Drive' onclick='launchDrive();'>

Google 代码示例:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Picker Example</title>

    <script type="text/javascript">

      var developerKey = 'xxxxxxxYYYYYYYY-12345678';
      var clientId = "1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com"
      var scope = ['https://www.googleapis.com/auth/photos'];

      var pickerApiLoaded = false;
      var oauthToken;

      function onApiLoad() 
        gapi.load('auth', 'callback': onAuthApiLoad);
        gapi.load('picker', 'callback': onPickerApiLoad);
      

      function onAuthApiLoad() 
        window.gapi.auth.authorize(
            
              'client_id': clientId,
              'scope': scope,
              'immediate': false
            ,
            handleAuthResult);
      

      function onPickerApiLoad() 
        pickerApiLoaded = true;
        createPicker();
      

      function handleAuthResult(authResult) 
        if (authResult && !authResult.error) 
          oauthToken = authResult.access_token;
          createPicker();
        
      

      // Create and render a Picker object for picking user Photos.
      function createPicker() 
        if (pickerApiLoaded && oauthToken) 
          var picker = new google.picker.PickerBuilder().
              addView(google.picker.ViewId.PHOTOS).
              setOAuthToken(oauthToken).
              setDeveloperKey(developerKey).
              setCallback(pickerCallback).
              build();
          picker.setVisible(true);
        
      

      // A simple callback implementation.
      function pickerCallback(data) 
        var url = 'nothing';
        if (data[google.picker.Response.ACTION] == google.picker.Action.PICKED) 
          var doc = data[google.picker.Response.DOCUMENTS][0];
          url = doc[google.picker.Document.URL];
        
        var message = 'You picked: ' + url;
        document.getElementById('result').innerHTML = message;
      
    </script>
  </head>
  <body>
    <div id="result"></div>

    <!-- The Google API Loader script. -->
    <script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
  </body>
</html>

2015 年 5 月 13 日编辑

除了杰森的回答,这也是我尝试过的(由 oncllick 按钮调用):

function launchDrive()
    
        //gapi.load('auth', 'callback': onAuthApiLoad);
        gapi.auth.init(onAuthApiLoad);
        gapi.load('picker', 'callback': onPickerApiLoad);
    

【问题讨论】:

【参考方案1】:

您将需要调用 gapi.auth.init。在此处查看文档:https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauthinit

初始化授权功能。在客户端加载时调用它,以防止弹出窗口阻止程序在 gapi.auth.authorize 调用上阻止身份验证窗口。

【讨论】:

杰森,感谢并为我迟到的回复道歉。正如我在问题中提到的那样,我已经尝试过了——我已经尝试过这个解决方案,但是得到 'TypeError: gapi.auth is undefined -- 我可以在我的代码中哪里使用它? 他们的例子肯定有问题。 30 分钟的快速入门代码和选择器代码一起破解,我至少得到了错误消息。在function onAuthApiLoad() 中,我需要添加gapi.client.init(clientId: clientId, scope: scope);,我现在使用按钮onclick 调用函数loadPicker,而不是在加载时自动调用。【参考方案2】:

我现在可以工作了。

在选择器的示例中,https://developers.google.com/picker/docs/,它调用:

 <script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>

在这个例子中,https://developers.google.com/api-client-library/javascript/start/start-js,它调用:

<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>

使用 client.js 修复了“TypeError: gapi.auth is undefined”问题,因此登录弹出窗口有效。

也许 api.js 是 API 的旧版本?

【讨论】:

你能详细说明一下吗?我有同样的问题,我不知道你是如何(或是否)使用为响应按钮按下而创建的选择器进行这项工作的。 是的,它可以通过 onclick 按钮事件起作用,请参阅我的问题中的 launchDrive() 函数。对我来说问题是文档不一致,一个说使用 api.js,另一个说使用 client.js,这解决了我的问题。【参考方案3】:

直接跳到底部

这是目前适用于我的代码。虽然这是我使用这个 API 的第一个小时,所以我真的不知道这些函数中的任何一个是做什么的,我也不知道正确的顺序和错误处理是什么,但至少现在这是功能性的。也许将来会对其他人有所帮助。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Picker Example</title>
</head>
<body style="width: 70%; margin: 100px auto;">

    <!-- Added a button to open picker -->
    <button onclick="loadPicker();" >Open from GoogleDrive</button>
    <div id="result"></div>

    <!-- Moved to end of body tag instead of head -->
    <script type="text/javascript">

    // The Browser API key obtained from the Google API Console.
    // Replace with your own Browser API key, or your own key.
    var developerKey = '<IDK WHAT'S SUPPOSED TO GO HERE, BUT ITS OK>';

    // The Client ID obtained from the Google API Console. Replace with your own Client ID.
    var clientId = "<YOUR CLIENT ID GOES HERE>.apps.googleusercontent.com"

    // Replace with your own project number from console.developers.google.com.
    // See "Project number" under "IAM & Admin" > "Settings"
    var appId = "<YOUR APP ID GOES HERE>";

    // Scope to use to access user's Drive items.
    var scope = ['https://www.googleapis.com/auth/drive'];

    var pickerApiLoaded = false;
    var oauthToken;

    // Use the Google API Loader script to load the google.picker script.
    function loadPicker() 
        // This needs to be client:auth2 no client
        gapi.load('client:auth2', 'callback': onAuthApiLoad);
        gapi.load('picker', 'callback': onPickerApiLoad);
    

    function onAuthApiLoad() 
        // we need to init gapi.client with the clientId and scope first
        gapi.client.init(
            clientId: clientId,
            scope: scope
        );

        // Now we can authorize? seems like the same thing here
        window.gapi.auth.authorize(
        
        'client_id': clientId,
        'scope': scope,
        'immediate': false
        ,
        handleAuthResult);
    

    function onPickerApiLoad() 
        pickerApiLoaded = true;
        createPicker();
    

    function handleAuthResult(authResult) 
        if (authResult && !authResult.error) 
            oauthToken = authResult.access_token;
            createPicker();
        
    

    // Create and render a Picker object for searching images.
    function createPicker() 
        // Wow this is a mess
        if (pickerApiLoaded && oauthToken) 
            var view = new google.picker.View(google.picker.ViewId.DOCS);
            view.setMimeTypes("image/png,image/jpeg,image/jpg");
            var picker = new google.picker.PickerBuilder()
            .enableFeature(google.picker.Feature.NAV_HIDDEN)
            .enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
            .setAppId(appId)
            .setOAuthToken(oauthToken)
            .addView(view)
            .addView(new google.picker.DocsUploadView())
            // Guess this is... optional?
            //.setDeveloperKey(developerKey)
            .setCallback(pickerCallback)
            .build();
            picker.setVisible(true);
        
    

    // A simple callback implementation.
    function pickerCallback(data) 
        if (data.action == google.picker.Action.PICKED) 
            var fileId = data.docs[0].id;
            alert('Selected fileId: ' + fileId);
        
    
    </script>

    <!-- The Google API Loader script. Removed the autorun -->
    <script type="text/javascript" src="https://apis.google.com/js/api.js"></script>
</body>
</html>

编辑:如果您得到一个未加载的弹出窗口,只需将其关闭并再次单击该按钮。这解决了我刚刚遇到的另一个问题。

再说一次,我还不知道自己在做什么,所以希望我能更好地理解这一点并在以后澄清事情。

E2:啊,关于 OAuth2 的更多信息可以在 Javascript GAPI 文档页面上找到,可以在这里找到:https://developers.google.com/api-client-library/javascript/features/authentication

从另一个文档中,gapi.load('client', callback) 似乎将加载 auth2(如果尚未加载)。调用 gapi.load('client:auth2', callback) 只会保存 1 个网络请求。

注意:当您使用 Oauth 2.0 授权您的应用程序时,您也不需要像第一个示例中那样设置 API 密钥。但是,这样做是一种很好的做法,以防您的代码扩展为处理未经授权的请求。

这就解释了为什么我可以删除 API/开发者密钥。

编辑 3:好的,上面的代码在技术上是错误的。

警告:不要将此方法与推荐的 gapi.auth2.init 和 signIn 流程一起使用。这是两种不同的行为(gapi.auth2.authorize 的授权与 gapi.auth2.init/signIn 的身份验证),如果在同一应用程序中使用,将会出现意外问题。

autorize 用于单次使用身份验证(例如,如果您登录了 2 个 google 帐户)。虽然使用gapi.init() 意味着用于更长期的会话(例如用于登录和退出网站)。

目前这是如何工作的,我不知道。


不要使用上面的代码,只是想记录一下进度。这是一个使用getAuthResponse()的更好的演示

<html>
  <head></head>
  <body>
    <div style="padding: 50px">
        <h2 style="color: #2196f3;">Status: <span id='status'></span></h2>
        <button id="signin-button" onclick="handleSignInClick()">Sign In</button>
        <button id="signout-button" onclick="handleSignOutClick()">Sign Out</button>
        <button id="signout-button" onclick="openFile()">Open File</button>
    </div>

    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script type="text/javascript">
    var cid = '<CLIENTID_HERE>';
    var scope = 'https://www.googleapis.com/auth/drive';
    var authenticated = false;
    var pickerLoaded = false;
    var auth = null;
    var user = null;
    var response = null;
    var token = null;
    var stat = $('#status');

      function openFile() 
        gapi.load('client:auth2', initClient);
        gapi.load('picker', onPickerLoad);
      

        function initClient() 
            stat.html("starting");

            gapi.client.init(
                clientId: cid,
                scope: scope
            ).then(
            function () 
                console.log("init");

                // Check if we are logged in.
                auth = gapi.auth2.getAuthInstance();
                auth.isSignedIn.listen(onStatusChange);
                authenticated = auth.isSignedIn.get();
                stat.html(authenticated);

                if (authenticated) 
                    stat.html("Logged In!");
                    user = auth.currentUser.get();
                    response = user.getAuthResponse(true);
                    token = response.access_token;
                    showPicker();
                 else 
                    stat.html("Logged Out!");
                
            , function()stat.html("error"));
        

        function onStatusChange(isSignedIn) 
            if (isSignedIn) 
                stat.html("Logged In!");
                authenticated = true;
                user = auth.currentUser.get();
                response = user.getAuthResponse(true);
                token = response.access_token;
                showPicker();
                showPicker();
             else 
                authenticated = false;
                stat.html("Logged Out!");

            
        

        function handleSignInClick(event) 
            gapi.auth2.getAuthInstance().signIn();
        

        function handleSignOutClick(event) 
            gapi.auth2.getAuthInstance().signOut();
            alert("signed out");
        


        function onPickerLoad() 
            pickerLoaded = true;
            showPicker();
        

        function showPicker() 
            if (pickerLoaded && authenticated) 
                var view = new google.picker.View(google.picker.ViewId.DOCS);
                var picker = new google.picker.PickerBuilder();
                picker.addView(view);
                picker.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
                picker.setOAuthToken(token);
                picker.setAppId()
                picker.setCallback(onDriveFileOpen);
                picker = picker.build();
                picker.setVisible(true);
            
        

        function onDriveFileOpen(data) 
            console.log(data);
            if (data.action == google.picker.Action.PICKED) 
                var fileId = data.docs[0].id;
                console.log(fileId);
                alert(data.docs[0].name);
            
        

    </script>
    <script async defer src="https://apis.google.com/js/api.js">
    </script>
  </body>
</html>

【讨论】:

【参考方案4】:

要解决您的问题,您需要了解 google 在您的情况下如何执行 oauth:

gapi 执行初始化操作 gapi 在新的弹出窗口中打开一个 google auth 页面,您在其中执行登录 登录成功后,gapi 会收到通知,您会收到令牌

为什么浏览器会在第二步中阻止弹出窗口:

窗口中不再存在原始事件(window.event 已销毁)。 用户手动阻止了当前站点的弹出窗口

因此,如果用户没有阻止弹出窗口并且您的弹出窗口仍然被阻止,gapi 操作看起来像:

<input type="button" onclick="auth" value="click"/>

function auth() 
  setTimeout(function() 
     // by this time window.event is destroyed, that's why browser blocks the popup
     window.open(document.URL, '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
  , 100)

那么你应该怎么做:

确保在单击按钮后不执行任何异步操作,例如 XHRequests 或其他 确保在用户单击按钮时已启动并准备好 gapi,因此当 gapi 需要创建弹出窗口时,window.event 不会为空。所以将所有 gapi init 方法移至DOMContentLoaded。 作为另一种选择,您可以使用服务器端 oauth 流,这意味着用户将在当前选项卡中被重定向到 gauth 页面,而不是弹出用户。

【讨论】:

以上是关于Google 选择器身份验证弹出窗口被阻止的主要内容,如果未能解决你的问题,请参考以下文章

如何在弹出窗口中通过 OAuth 2.0 向 Google 进行身份验证?

如何将自定义消息添加到 Firebase Google 身份验证弹出窗口?

对 Google 协作平台提要/API 的 CORS 身份验证请求被阻止

谷歌 BigQuery 身份验证

在弹出窗口中打开 Google SignIn 用户身份验证,需要使用 OAuth 2.0 在同一选项卡本身中打开

使用 MFP 身份验证框架登录时访问被拒绝弹出窗口