Google API 客户 "refresh token must be passed in or set as part of setAccessToken"

Google API Client "refresh token must be passed in or set as part of setAccessToken"

我目前面临一个非常奇怪的问题,实际上我一直在遵循 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,这样我就可以 "disable" 它而无需修改太多代码:

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

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

有问题

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

我在使用新 google api 库时遇到了同样的问题。搜索解决方案带来了以下 link:

根据这些信息,我修改了快速入门代码部分以满足我的需要。在使用 Google 首次授权后,我获得了驱动器-php-quickstart.json,其中包含 refresh_token,该驱动器将在 3600 秒或一小时后过期。 refresh token 只发放一次,所以如果丢了,就需要重新授权。 所以,为了让它始终在驱动器中-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)); 
}

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()));
}

在我的例子中,我忘记将访问类型设置为 "offline",否则不会生成刷新令牌。

$client->setAccessType('offline');

完成后,google 文档中给出的示例代码将起作用。

// 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()));
}

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

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

我解释..... 没有返回刷新令牌,因为我们没有强制执行批准提示。离线模式是不够的。我们必须强制批准提示。此外,必须在这两个选项之前设置 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;
}

只是为遇到此消息问题的任何人提供一些更新,主要是因为只有第一个命令 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));
}

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

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

背景故事:

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

这个错误:

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

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

$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

为什么失败了?长话短说,当我打印出 $accessToken 数组时,我意识到它来自解码这个 json 文件(根据快速入门代码,你 posted/that 来自 google)

credentials/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 的有效值,所以如果您这样做,错误应该会消失。我还通过反馈 link 更新了作者,让他们知道这一点,以防其他 php 开发者遇到这个问题。希望这对某人有帮助。如果我对 post 的格式设置不当,我深表歉意,我是 S.E 的新手。我只是想分享一下,因为我终于成功了。

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

// 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() returns 为 null,如果您直接提供 $accessToken,它将正常工作并更新您的文件,希望它能解决某些问题。

写入credentialsPath时需要序列化accestoken

 // 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;
}

我的建议是在获取访问令牌后立即将刷新令牌保存到.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;
}