GAPI 的 OAuth - 首次登录 Javascript 后避免身份验证和授权

2024-02-25

我创建了一个 chrome 扩展,可以读取电子邮件、执行某些操作并使用 javascript 的 google 客户端 API 创建任务。 我使用 chrome 身份进行身份验证和授权。 扩展按预期工作。然而,它每隔一段时间就会要求签名。我想要的是在后台授权用户脚本,以便他们不需要在初始身份验证和授权后一遍又一遍地执行此操作。

到目前为止我所做的:

  • 我读到我需要一个刷新令牌来避免这种情况。但是,刷新令牌预计将在服务器端而不是客户端上进行交换和存储(这不起作用,因为后台脚本正在客户端执行此操作)
  • 将gapi.auth.authorize 与立即true 一起使用。这会产生有关外部可见性的错误。当我读到其他内容时,他们建议在服务器内使用它。我不知道如何在 chrome 扩展中做到这一点。
  • 在 getAuthToken 中将交互设置为 false,由于访问令牌过期后出现身份验证问题,它开始给出错误 401。

以下是我用于身份验证和授权的代码,在加载 google api 的客户端 js 文件后调用 onGoogleLibraryLoaded 函数。

    var signin = function (callback) {
        chrome.identity.getAuthToken({interactive: true}, callback);
    };

    function onGoogleLibraryLoaded() {
        signin(authorizationCallback);
    }

    var authorizationCallback = function (data) {
        gapi.auth.setToken({access_token: data});
        gapi.client.load('tasks', 'v1')
        gapi.client.load('gmail', 'v1', function () {

            console.log("Doing my stuff after this ..")
        });
    };

更新: 根据答案中的建议,我对代码做了一些更改。但是,我仍然面临同样的问题。以下是更新后的代码片段

jQuery.loadScript = function (url, callback) {
    jQuery.ajax({
        url: url,
        dataType: 'script',
        success: callback,
        async: false

   });
}
//This is the first thing that happens. i.e. loading the gapi client 
if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js', 
    function(){
    console.log("gapi script loaded...")
});

//Every 20 seconds this function runs with internally loads the tasks and gmail 
// Once the gmail module is loaded it calls the function getLatestHistoryId()
setInterval(function() {
    gapi.client.load('tasks', 'v1')
    gapi.client.load('gmail', 'v1', function(){
        getLatestHistoryId()
    })
    // your code goes here...
}, 20 * 1000); // 60 * 1000 milsec

// This is the function that will get user's profile and when the response is received 
// it'll check for the error i.e. error 401 through method checkForError
function getLatestHistoryId(){
  prevEmailData = []

  var request = gapi.client.gmail.users.getProfile({
        'userId': 'me'
    });
    request.execute(function(response){
      console.log("User profile response...")
      console.log(response)
      if(checkForError(response)){
        return
      }
    })
}

// Now here I check for the 401 error. If there's a 401 error 
// It will call the signin method to get the token again. 
// Before calling signin it'll remove the saved token from cache through removeCachedAuthToken
// I have also tried doing it without the removeCachedAuthToken part. However the results were the same.  
// I have left console statements which are self-explanatory
function checkForError(response){
  if("code" in response && (response["code"] == 401)){
    console.log(" 401 found will do the authentication again ...")
    oooAccessToken = localStorage.getItem("oooAccessTokenTG")
    console.log("access token ...")
    console.log(oooAccessToken)
    alert("401 Found Going to sign in ...")

    if(oooAccessToken){
        chrome.identity.removeCachedAuthToken({token: oooAccessToken}, function(){
        console.log("Removed access token")
        signin()
      })  
    }
    else{
      console.log("No access token found to be remove ...")
      signin()
    }
    return true
  }
  else{
    console.log("Returning false from check error")
    return false
  }
}

// So finally when there is 401 it returns back here and calls 
// getAuthToken with interactive true 
// What happens here is that everytime this function is called 
// there is a signin popup i.e. the one that asks you to select the account and allow permissions
// That's what is bothering me. 
// I have also created a test chrome extension and uploaded it to chrome web store. 
// I'll share the link for it separately. 

var signin = function (callback) {
    console.log(" In sign in ...")
    chrome.identity.getAuthToken({interactive: true}, function(data){
        console.log("getting access token without interactive ...")
        console.log(data)

        gapi.auth.setToken({access_token: data});
        localStorage.setItem("oooAccessTokenTG", data)

        getLatestHistoryId()
    })
};

清单是这样的:

{
  "manifest_version": 2,

  "name": "Sign in Test Extension ",
  "description": "",
  "version": "0.0.0.8",
  "icons": {
      "16": "icon16.png", 
      "48": "icon48.png", 
      "128": "icon128.png" 
  },
  "content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
  "browser_action": {
   "default_icon": "icon.png",
   "default_popup": "popup.html"
  },
  "permissions": [   
    "identity",
    "storage"
   ],

  "oauth2": {
        "client_id": "1234.apps.googleusercontent.com",
        "scopes": [
            "https://www.googleapis.com/auth/gmail.readonly"
        ]
    },
    "background":{
      "scripts" : ["dependencies/jquery.min.js", "background.js"]
    }
}

还有其他人面临同样的问题吗?


所以我相信这就是我问题的答案。

需要了解的一些重要事项

  • Chrome 登录与 Gmail 登录不同。您可以让 UserA 登录 Chrome,而您计划与 UserB 一起使用 Chrome 扩展程序。在这种情况下 chrome.identity.getAuthToken 将不起作用,因为它正在寻找登录 Chrome 的用户。
  • 要使用其他 Google 帐户(即未登录 Chrome 的帐户),您需要使用 chrome.identity.launchWebAuthFlow。以下是您可以使用的步骤。我指的是这里给出的例子(是否可以使用 Chrome App Indentity Api 获取 Id 令牌? https://stackoverflow.com/questions/26256179/is-it-possible-to-get-an-id-token-with-chrome-app-indentity-api/32548057#32548057)

  1. 转到 google 控制台,创建您自己的项目 > 凭据 > 创建凭据 > OAuthClientID > Web 应用程序。在该页面的授权重定向 URI 字段中,输入 https://.chromiumapp.org 格式的重定向 URL。如果您不知道 Chrome 扩展 ID 是什么,请参阅此(Chrome 扩展程序 ID - 如何找到它 https://stackoverflow.com/questions/8946325/chrome-extension-id-how-to-find-it)
  2. 这将生成一个客户端 ID,该 ID 将进入您的清单文件中。忘记您之前可能创建的任何客户端 ID。假设在我们的示例中客户端 ID 是9999.apps.googleusercontent.com

清单文件:

    {
      "manifest_version": 2,
      "name": "Test gmail extension 1",
      "description": "description",
      "version": "0.0.0.1",
      "content_security_policy": "script-src 'self' 'unsafe-eval' https://apis.google.com; object-src 'self'",
      "background": {
        "scripts": ["dependencies/jquery.min.js", "background.js"]
      },
      "browser_action": {
       "default_icon": "icon.png",
       "default_popup": "popup.html"
      },
      "permissions": [
        "identity",
        "storage"

      ],
      "oauth2": {
        "client_id": "9999.apps.googleusercontent.com",
        "scopes": [
          "https://www.googleapis.com/auth/gmail.readonly",
           "https://www.googleapis.com/auth/tasks"
        ]
      }
    }

在background.js中获取用户信息的示例代码

    jQuery.loadScript = function (url, callback) {
        jQuery.ajax({
            url: url,
            dataType: 'script',
            success: callback,
            async: false
       });
    }
    // This is the first thing that happens. i.e. loading the gapi client 
    if (typeof someObject == 'undefined') $.loadScript('https://apis.google.com/js/client.js', 
        function(){
        console.log("gapi script loaded...")
    });


    // Every xx seconds this function runs with internally loads the tasks and gmail 
    // Once the gmail module is loaded it calls the function getLatestHistoryId()
    setInterval(function() {

        gapi.client.load('tasks', 'v1')
        gapi.client.load('gmail', 'v1', function(){
            getLatestHistoryId()
        })
        // your code goes here...
    }, 10 * 1000); // xx * 1000 milsec

    // This is the function that will get user's profile and when the response is received 
    // it'll check for the error i.e. error 401 through method checkForError
    // If there is no error i.e. the response is received successfully 
    // It'll save the user's email address in localstorage, which would later be used as a hint
    function getLatestHistoryId(){
      var request = gapi.client.gmail.users.getProfile({
            'userId': 'me'
        });
        request.execute(function(response){
          console.log("User profile response...")
          console.log(response)
          if(checkForError(response)){
            return
          }
            userEmail = response["emailAddress"]
            localStorage.setItem("oooEmailAddress", userEmail);
        })
    }

    // Now here check for the 401 error. If there's a 401 error 
    // It will call the signin method to get the token again. 
    // Before calling the signinWebFlow it will check if there is any email address 
    // stored in the localstorage. If yes, it would be used as a login hint.  
    // This would avoid creation of sign in popup in case if you use multiple gmail accounts i.e. login hint tells oauth which account's token are you exactly looking for
    // The interaction popup would only come the first time the user uses your chrome app/extension
    // I have left console statements which are self-explanatory
    // Refer the documentation on https://developers.google.com/identity/protocols/OAuth2UserAgent >
    // Obtaining OAuth 2.0 access tokens > OAUTH 2.0 ENDPOINTS for details regarding the param options
    function checkForError(response){
      if("code" in response && (response["code"] == 401)){
        console.log(" 401 found will do the authentication again ...")
        // Reading the data from the manifest file ...
        var manifest = chrome.runtime.getManifest();

        var clientId = encodeURIComponent(manifest.oauth2.client_id);
        var scopes = encodeURIComponent(manifest.oauth2.scopes.join(' '));
        var redirectUri = encodeURIComponent('https://' + chrome.runtime.id + '.chromiumapp.org');
        // response_type should be token for access token
        var url = 'https://accounts.google.com/o/oauth2/v2/auth' + 
                '?client_id=' + clientId + 
                '&response_type=token' + 
                '&redirect_uri=' + redirectUri + 
                '&scope=' + scopes

        userEmail = localStorage.getItem("oooEmailAddress")
        if(userEmail){
            url +=  '&login_hint=' + userEmail
        } 

        signinWebFlow(url)
        return true
      }
      else{
        console.log("Returning false from check error")
        return false
      }
    }


    // Once you get 401 this would be called
    // This would get the access token for user. 
    // and than call the method getLatestHistoryId again 
    async function signinWebFlow(url){
        console.log("THE URL ...")
        console.log(url)
        await chrome.identity.launchWebAuthFlow(
            {
                'url': url, 
                'interactive':true
            }, 
            function(redirectedTo) {
                if (chrome.runtime.lastError) {
                    // Example: Authorization page could not be loaded.
                    console.log(chrome.runtime.lastError.message);
                }
                else {
                    var response = redirectedTo.split('#', 2)[1];
                    console.log(response);

                    access_token = getJsonFromUrl(response)["access_token"]
                    console.log(access_token)
                    gapi.auth.setToken({access_token: access_token});
                    getLatestHistoryId()
                }
            }
        );
    }

    // This is to parse the get response 
    // referred from https://stackoverflow.com/questions/8486099/how-do-i-parse-a-url-query-parameters-in-javascript
    function getJsonFromUrl(query) {
      // var query = location.search.substr(1);
      var result = {};
      query.split("&").forEach(function(part) {
        var item = part.split("=");
        result[item[0]] = decodeURIComponent(item[1]);
      });
      return result;
    }

如果您有任何疑问,请随时与我联系。我花了好几天的时间来加入这些点。我不希望其他人也这样做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

GAPI 的 OAuth - 首次登录 Javascript 后避免身份验证和授权 的相关文章

随机推荐