具有公共访问权限的 Web 应用程序的 Google OAuth 2.0 刷新令牌

Posted

技术标签:

【中文标题】具有公共访问权限的 Web 应用程序的 Google OAuth 2.0 刷新令牌【英文标题】:Google OAuth 2.0 refresh token for web application with public access 【发布时间】:2014-10-20 22:31:50 【问题描述】:

我收到以下错误:

OAuth 2.0 访问令牌已过期,刷新令牌不可用。自动批准的响应不会返回刷新令牌

我有一个只有我的服务器才能访问的 Web 应用程序,初始身份验证工作正常,但一个小时后,弹出上述错误消息。我的脚本如下所示:

require_once 'Google/Client.php';     
require_once 'Google/Service/Analytics.php';       
session_start();

$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");
$client->setDeveloperKey("MY_API_KEY");
$client->setClientId('MY_CLIENT_ID.apps.googleusercontent.com');
$client->setClientSecret('MY_KEY');
$client->setRedirectUri('MY_REDIRECT_URI');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));

if (isset($_GET['code'])) 
    $client->authenticate($_GET['code']);  
    $_SESSION['token'] = $client->getAccessToken();
    $redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
    header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));


if (!$client->getAccessToken() && !isset($_SESSION['token'])) 
    $authUrl = $client->createAuthUrl();
    print "<a class='login' href='$authUrl'>Connect Me!</a>";

如何为此设置刷新令牌?

编辑 1:我也尝试使用服务帐户执行此操作。我遵循了 GitHub 上提供的文档:

https://github.com/google/google-api-php-client/blob/master/examples/service-account.php

我的脚本如下所示:

session_start();
include_once "templates/base.php";
require_once 'Google/Client.php';
require_once 'Google/Service/Gmail.php';
$client_id = 'MY_CLIENT_ID.apps.googleusercontent.com'; 
$service_account_name = 'MY_EMAIL_ADDRESS@developer.gserviceaccount.com ';
$key_file_location = 'Google/MY_KEY_FILE.p12';
echo pageHeader("Service Account Access");
    if ($client_id == ''
|| !strlen($service_account_name)
|| !strlen($key_file_location)) 
  echo missingServiceAccountDetailsWarning();

$client = new Google_Client();
$client->setApplicationName("Client_Library_Examples");
$service = new Google_Service_Gmail($client);
if (isset($_SESSION['service_token'])) 
  $client->setAccessToken($_SESSION['service_token']);

$key = file_get_contents($key_file_location);
$cred = new Google_Auth_AssertionCredentials(
    $service_account_name,
    array('https://www.googleapis.com/auth/gmail.readonly'),
    $key
);
$client->setAssertionCredentials($cred);
if($client->getAuth()->isAccessTokenExpired()) 
  $client->getAuth()->refreshTokenWithAssertion($cred);

这将返回以下消息:

刷新 OAuth2 令牌时出错,消息:' "error" : "invalid_grant" ''

跟踪此代码到其源代码后,可以在Google/Auth/OAuth2.php找到

有问题的方法,refreshTokenRequest

private function refreshTokenRequest($params)

    $http = new Google_Http_Request(
    self::OAUTH2_TOKEN_URI,
    'POST',
    array(),
    $params
    );
    $http->disableGzip();
    $request = $this->client->getIo()->makeRequest($http);

    $code = $request->getResponseHttpCode();
    $body = $request->getResponseBody();
    if (200 == $code) 
      $token = json_decode($body, true);
      if ($token == null) 
        throw new Google_Auth_Exception("Could not json decode the access token");
      

      if (! isset($token['access_token']) || ! isset($token['expires_in']))     
        throw new Google_Auth_Exception("Invalid token format");
      

      if (isset($token['id_token'])) 
        $this->token['id_token'] = $token['id_token'];
      
      $this->token['access_token'] = $token['access_token'];
      $this->token['expires_in'] = $token['expires_in'];
      $this->token['created'] = time();
     else 
      throw new Google_Auth_Exception("Error refreshing the OAuth2 token, message: '$body'", $code);
    
  

这意味着$code 变量为NULL。 我在 SO 上找到了这篇文章:

Error refreshing the OAuth2 token “error” : “invalid_grant”

并且可以看到仍然没有首选的解决方案。 这让我发疯了。这方面的文档很少甚至没有,如果有人有解决方案,我相信我不是唯一一个在寻找它的人。

【问题讨论】:

【参考方案1】:

每个access_token在几秒后过期,需要通过refresh_token刷新,“离线访问”就是你要找的。您可以在此处查看文档:

https://developers.google.com/accounts/docs/OAuth2WebServer#offline

要获得一个 refresh_token,您只需运行一次此代码:

require_once 'Google/Client.php';

$client = new Google_Client();
$client->setClientId('MY_CLIENT_ID.apps.googleusercontent.com');
$client->setClientSecret('MY_KEY');
$client->setRedirectUri('MY_REDIRECT_URI');
//next two line added to obtain refresh_token
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));

if (isset($_GET['code'])) 
    $credentials = $client->authenticate($_GET['code']);  

    /*TODO: Store $credentials somewhere secure */

 else 
    $authUrl = $client->createAuthUrl();
    print "<a class='login' href='$authUrl'>Connect Me!</a>";

$credentials 包括访问令牌和刷新令牌。您必须存储它以随时获取新的访问令牌。所以当你想调用 api 时:

require_once 'Google/Client.php';
require_once 'Google/Service/Gmail.php';

/*TODO: get stored $credentials */

$client = new Google_Client();
$client->setClientId('MY_CLIENT_ID.apps.googleusercontent.com');
$client->setRedirectUri('MY_REDIRECT_URI');
$client->setClientSecret('MY_KEY');
$client->setScopes(array('https://www.googleapis.com/auth/gmail.readonly'));
$client->setAccessToken($credentials);

$service = new Google_Service_Gmail($client);

【讨论】:

非常感谢!完美运行。我要求对帖子进行编辑以删除您在第二个 API 调用中 json_decode $credentialsJson 的位,因为 OAuth2.php setAccessToken 第 179 行为您执行此操作,如果您事先执行此操作,则会引发致命错误。 是的,你是对的,我还删除了错误的第一个脚本的第 14 行。我想json_encode($credentials)authenticate 为我们做这件事。 $access_token = $client-&gt;getAccessToken();$credentials = $client-&gt;authenticate($_GET['code']); 相同。我的 $credentials 不包含 refresh_token 值。只包含access_token、token_type、expires_in、created @Mutlu 正如我所说,你必须设置 $client-&gt;setAccessType('offline');$client-&gt;setApprovalPrompt('force'); 来强制你的 $credentials 包含 refresh_token。

以上是关于具有公共访问权限的 Web 应用程序的 Google OAuth 2.0 刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

单流:通过 Google oAuth 登录用户并授予离线/服务器访问权限?

如何建立从GAE(具有公共访问权限)到私有GKE集群的安全连接。

将对象保存在具有公共访问权限的 AWS S3 存储桶中

私有的嵌套类(内部或静态)是不是可能具有具有公共访问权限的方法?

为啥继承的受保护运算符=()具有公共访问权限

为具有公共访问权限的文件上传生成预签名 URL