Google API 客户端“刷新令牌必须传入或设置为 setAccessToken 的一部分”

Posted

技术标签:

【中文标题】Google API 客户端“刷新令牌必须传入或设置为 setAccessToken 的一部分”【英文标题】:Google API Client "refresh token must be passed in or set as part of setAccessToken" 【发布时间】:2017-01-11 21:27:36 【问题描述】:

我目前面临一个非常奇怪的问题,实际上我一直在遵循 Google API 文档中的相同指南 (https://developers.google.com/google-apps/calendar/quickstart/php)。我尝试了两次,第一次它就像一个魅力,但在访问令牌过期后,Google API Doc 直接提供的脚本无法刷新它。

TL;DR

这是错误信息:

sam@ssh:~$ php www/path/to/app/public/quickstart.php


Fatal error: Uncaught exception 'LogicException' with message 'refresh token must be passed in or set as part of setAccessToken' in /home/pueblo/www/path/to/app/vendor/google/apiclient/src/Google/Client.php:258
Stack trace:
#0 /home/pueblo/www/path/to/app/public/quickstart.php(55): Google_Client->fetchAccessTokenWithRefreshToken(NULL)
#1 /home/pueblo/www/path/to/app/public/quickstart.php(76): getClient()
#2 main
  thrown in /home/pueblo/www/path/to/app/vendor/google/apiclient/src/Google/Client.php on line 258

这是我修改过的google php脚本的一部分:

require_once __DIR__ . '/../vendor/autoload.php';

// I don't want the creds to be in my home folder, I prefer them in the app's root
define('APPLICATION_NAME', 'LRS API Calendar');
define('CREDENTIALS_PATH', __DIR__ . '/../.credentials/calendar-php-quickstart.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/../client_secret.json');

我还修改了expandHomeDirectory,这样我就可以在不修改太多代码的情况下“禁用”它:

function expandHomeDirectory($path) 
  $homeDirectory = getenv('HOME');
  if (empty($homeDirectory)) 
    $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
  
  return $path;
  // return str_replace('~', realpath($homeDirectory), $path);

所以为了检查我是否错了或者谷歌是否错了,我做了一个实验:昨天晚上我从 ssh 启动了快速启动脚本来检查它是否工作,确实是这样,所以我决定今天早上检查它是否仍然像我睡前一样工作,但我认为谷歌的quickstart.php 有问题。

我希望有人可以帮助我,我已经检查了有关该主题的所有其他帖子,但它们都已过时。

【问题讨论】:

我觉得这个SO question可以帮到你。 不,确实它看起来像是一个有效的答案,但是这个用户报告的错误已经在我使用的代码中得到修复,因为他向谷歌提交了修复它的票。但是感谢您尝试帮助我:) 截至 2017 年 11 月 20 日,作为 quickstart.php 发布的 Google 示例 PHP 代码仍然出现您询问的错误... 【参考方案1】:

我在使用新的 google api 库时遇到了同样的问题。搜索解决方案带来了以下链接: RefreshToken Not getting send back after I get new token google sheets API

根据这些信息,我修改了快速入门代码部分以满足我的需要。在获得 Google 的首次授权后,我获得了 drive-php-quickstart.json,其中包含在 3600 秒或一小时内到期的 refresh_token。刷新令牌只发出一次,因此如果丢失,则需要重新授权。 所以,为了让它始终在 drive-php-quickstart.json 中,我已经完成了以下操作:

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) 
// save refresh token to some variable
$refreshTokenSaved = $client->getRefreshToken(); 

// update access token
$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved); 

// pass access token to some variable
$accessTokenUpdated = $client->getAccessToken();

// append refresh token
$accessTokenUpdated['refresh_token'] = $refreshTokenSaved;

// save to file
file_put_contents($credentialsPath, json_encode($accessTokenUpdated)); 

【讨论】:

感谢您的回答!我会看看它是否是一个可行的解决方案,因为它会很有帮助。不过我现在很忙,所以不要指望回复太快。 @SamuelPrevost 我正在使用 Sheet API v4.. 具有相同的代码,但它仍然给出相同的错误“刷新令牌必须传入或设置为 setAccessToken 的一部分” @HamzaZafeer 你试过这个帖子里的所有回复了吗?我不知道如何帮助你,伙计,已经一年了。我什至不知道“Sheet API v4”是什么。只需备份您当前的设置,然后在此处尝试所有解决方案并停止,直到找到有效的解决方案。祝你好运。【参考方案2】:

Google 更新了他们的PHP Quickstart,改进了处理此问题的方法:

以下片段:

// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);

// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) 
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));

【讨论】:

我使用更新的快速入门,但它仍然给我错误?为什么? 截至 2017 年 11 月 20 日,作为 quickstart.php 发布的 Google 示例 PHP 代码仍然存在原发帖人询问的错误。【参考方案3】:

在我的情况下,我忘记将访问类型设置为“离线”,否则不会生成刷新令牌。

$client->setAccessType('offline');

一旦完成,谷歌文档中给出的示例代码就可以工作了。

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) 
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));

【讨论】:

【参考方案4】:

我最近遇到了同样的问题,我用这个解决了。

<?php
 $client->setRedirectUri($this->_redirectURI);
 $client->setAccessType('offline');
 $client->setApprovalPrompt('force');

我解释...... 没有返回刷新令牌,因为我们没有强制批准提示。离线模式是不够的。我们必须强制approvalPrompt。此外,必须在这两个选项之前设置 redirectURI。它对我有用。

这是我的全部功能

<?php
     private function getClient()
     
        $client = new Google_Client();
        $client->setApplicationName($this->projectName);
        $client->setScopes(SCOPES);
        $client->setAuthConfig($this->jsonKeyFilePath);
        $client->setRedirectUri($this->redirectUri);
        $client->setAccessType('offline');
        $client->setApprovalPrompt('force');

       // Load previously authorized credentials from a file.
       if (file_exists($this->tokenFile)) 
         $accessToken = json_decode(file_get_contents($this->tokenFile), 
         true);
       else 
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));

        if (isset($_GET['code'])) 
            $authCode = $_GET['code'];
            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            header('Location: ' . filter_var($this->redirectUri, 
            FILTER_SANITIZE_URL));
            if(!file_exists(dirname($this->tokenFile))) 
                mkdir(dirname($this->tokenFile), 0700, true);
            

            file_put_contents($this->tokenFile, json_encode($accessToken));
        else
            exit('No code found');
        
    
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) 

        // save refresh token to some variable
        $refreshTokenSaved = $client->getRefreshToken();

        // update access token
        $client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

        // pass access token to some variable
        $accessTokenUpdated = $client->getAccessToken();

        // append refresh token
        $accessTokenUpdated['refresh_token'] = $refreshTokenSaved;

        //Set the new acces token
        $accessToken = $refreshTokenSaved;
        $client->setAccessToken($accessToken);

        // save to file
        file_put_contents($this->tokenFile, 
       json_encode($accessTokenUpdated));
    
    return $client;

【讨论】:

感谢您的回答!我希望它会帮助很多人。我已经切换到 Apple iCloud 的 CalDAV API,因为它使用起来更简单(没有复杂的令牌……)但无论如何谢谢^^ 很高兴 不要忘记,每次更新访问令牌时,您都需要在保存的文件中设置访问令牌和创建时间。 $client-&gt;setTokenCallback(...) 每次客户端(自动)刷新它时为您提供一个挂钩来存储新令牌。如果您不这样做,那么您最终会在每次 API 调用中刷新令牌,从初始授权后一小时开始。如果每天只进行一次预定的 API 调用,这可能没问题,但如果运行得更频繁,则会增加很多开销。 @UlrichDohou 您写“还必须在这两个选项之后设置redirectURI”,但在您的示例中,redirectURI 在这两个选项之前。 ?? @Jason 用新的访问令牌覆盖旧的访问令牌怎么样?然后你得到最新的访问令牌,就是这样......?【参考方案5】:

对于任何对此消息有问题的人来说只是一些更新,主要是因为只有第一个命令 fetchAccessTokenWithAuthCode() 生成包含刷新令牌的凭证(技术上永久有效 - 如果您不撤销它,则没有 2 小时有效期)。当您获得新的令牌时,它会替换原来的令牌,但它不包含所需的刷新令牌,因此下次您需要更新令牌时,它将崩溃。这可以通过替换刷新功能轻松解决,例如:

  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) 
    $oldAccessToken=$client->getAccessToken();
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    $accessToken=$client->getAccessToken();
    $accessToken['refresh_token']=$oldAccessToken['refresh_token'];
    file_put_contents($credentialsPath, json_encode($accessToken));

现在,每次您更新访问令牌时,您的刷新令牌也会被传递下去。

【讨论】:

【参考方案6】:

我遇到了同样的问题,终于开始工作了:

背景故事:

我收到了同样的错误。这是我发现的:

这个错误:

PHP 致命错误:未捕获的 LogicException:刷新令牌必须传入或设置为 /Library/WebServer/Documents/Sites/test/scripts/vendor/google/apiclient/src 中 setAccessToken 的一部分/Google/Client.php:267

正在引用更新访问令牌(又名刷新)方法:

$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

为什么会失败?长话短说,当我打印出 $accessToken 数组时,我意识到该数组来自解码这个 json 文件(根据您发布的快速入门代码/来自谷歌)

凭证/calendar-php-quickstart.json

我发现错误是由于我 print_r 时 accessToken 数组的打印方式:

数组 ( [access_token] => 数组 ( [access_token] => xyz123 [token_type] => 持有者 [expires_in] => 3600 [refresh_token] => xsss112222 [创建] => 1511379484 )

)

解决方案:

$refreshToken = $accessToken["access_token"]["refresh_token"];

就在这一行之前:

    $client->fetchAccessTokenWithRefreshToken($refreshToken);

当令牌在一个小时后过期时,我终于可以根据需要刷新令牌了。我认为本文的开发人员假设数组打印为:

数组 ( [access_token] => xyz123 [token_type] => 持有者 [expires_in] => 3600 [refresh_token] => xsss112222 [创建] => 1511379484 )

所以他们认为你可以简单地做 $accessToken["refresh_token"];这是不正确的。

现在我们有一个有效的 $refreshToken 值,所以如果你这样做,错误应该会消失。我还通过反馈链接更新了作者,让他们知道这一点,以防其他 php 开发人员遇到这个问题。希望这可以帮助某人。如果我对这篇文章的格式不好,我深表歉意,我是 S.E. 的新手。我只是想分享一下,因为我终于让它工作了。

【讨论】:

【参考方案7】:

所以在查看这段代码一段时间后:

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) 
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));

所需要的就是这个改变:

// Exchange authorization code for an access token.
// "refresh_token" is returned along with the access token
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);


// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) 
    $client->fetchAccessTokenWithRefreshToken($accessToken);
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));

由于这个函数 $client->getRefreshToken() 返回 null,如果你直接提供 $accessToken,它会正常工作并更新你的文件,希望它能解决一些问题。

【讨论】:

【参考方案8】:

accestoken写到credentialsPath时需要序列化。

 // Exchange authorization code for an access token.
    $accessToken = $client->authenticate($authCode);

    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) 
        mkdir(dirname($credentialsPath), 0700, true);
    
    $serArray = serialize($accessToken);
    file_put_contents($credentialsPath, $serArray);
    printf("Credentials saved to %s\n", $credentialsPath);

当你从文件中读取时,你需要反序列化它。

if (file_exists($credentialsPath)) 
    $unserArray =  file_get_contents($credentialsPath);
    $accessToken = unserialize($unserArray);


功能齐全

function getClient() 
    $client = new Google_Client();
    // Set to name/location of your client_secrets.json file.
    $client->setAuthConfigFile('client_secret.json');
    // Set to valid redirect URI for your project.
    $client->setRedirectUri('http://localhost');
    $client->setApprovalPrompt('force');

    $client->addScope(Google_Service_YouTube::YOUTUBE_READONLY);
    $client->setAccessType('offline');

    // Load previously authorized credentials from a file.
    $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);


    if (file_exists($credentialsPath)) 
        $unserArray =  file_get_contents($credentialsPath);
        $accessToken = unserialize($unserArray);

     else 
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        printf("Open the following link in your browser:\n%s\n", $authUrl);
        print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));

        // Exchange authorization code for an access token.
        $accessToken = $client->authenticate($authCode);

        // Store the credentials to disk.
        if(!file_exists(dirname($credentialsPath))) 
            mkdir(dirname($credentialsPath), 0700, true);
        
        $serArray = serialize($accessToken);
        file_put_contents($credentialsPath, $serArray);
        printf("Credentials saved to %s\n", $credentialsPath);
    

    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) 
        $client->refreshToken($client->getRefreshToken());
        file_put_contents($credentialsPath, $client->getAccessToken());
    
    return $client;

【讨论】:

【参考方案9】:

我的建议是在获取访问令牌后立即将刷新令牌保存到 .json,如果访问令牌过期,请使用刷新令牌。

在我的项目中是这样工作的:

public static function getClient()

    $client = new Google_Client();
    $client->setApplicationName('JhvInformationTable');
    $client->setScopes(Google_Service_Calendar::CALENDAR_READONLY);
    $client->setAuthConfig('credentials.json');
    $client->setAccessType('offline');

    // Load previously authorized credentials from a file.
    $credentialsPath = 'token.json';
    $credentialsPath2 = 'refreshToken.json';
    if (file_exists($credentialsPath)) 
        $accessToken = json_decode(file_get_contents($credentialsPath), true);
     else 
        // Request authorization from the user.
        $authUrl = $client->createAuthUrl();
        //printf("Open the following link in your browser:\n%s\n", $authUrl);
        //print 'Enter verification code: ';
        $authCode = trim(fgets(STDIN));

        //echo "<script> location.href='".$authUrl."'; </script>";
        //exit;

        $authCode ='********To get code, please uncomment the code above********';

        // Exchange authorization code for an access token.
        $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
        $refreshToken = $client->getRefreshToken();

        // Check to see if there was an error.
        if (array_key_exists('error', $accessToken)) 
            throw new Exception(join(', ', $accessToken));
        

        // Store the credentials to disk.
        if (!file_exists(dirname($credentialsPath))) 
            mkdir(dirname($credentialsPath), 0700, true);
        
        file_put_contents($credentialsPath, json_encode($accessToken));
        file_put_contents($credentialsPath2, json_encode($refreshToken));
        printf("Credentials saved to %s\n", $credentialsPath);
    
    $client->setAccessToken($accessToken);

    // Refresh the token if it's expired.
    if ($client->isAccessTokenExpired()) 
        $refreshToken = json_decode(file_get_contents($credentialsPath2), true);
        $client->fetchAccessTokenWithRefreshToken($refreshToken);
        file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
    
    return $client;

【讨论】:

以上是关于Google API 客户端“刷新令牌必须传入或设置为 setAccessToken 的一部分”的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有 Oauth 的情况下访问 Google 客户端 API(仅 API 密钥)

Google+ 与 Google 身份平台 API

带有 Google JavaScript 客户端库的 Google Contacts API

使用 Google API Java 客户端库刷新令牌

Google BigQuery 和 Google API 客户端包

使用 google people API 访问 google 连接