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


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

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

TL,博士

错误信息如下:

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

这是我从谷歌修改的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有问题。

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

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

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

我的建议是在获得访问令牌后立即保存刷新令牌到.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库时遇到了同样的问题。搜索解决方案带来了以下链接:在我得到新的令牌google sheets API后,RefreshToken没有被发送回来

基于这些信息,我修改了快速入门代码部分以满足我的需要。经过Google的首次授权,我获得了drive-php-quickstart。Json,包含refresh_token,将在3600秒或一小时内过期。刷新令牌只发出一次,因此如果它丢失,则需要重新授权。它总是在drive-php-quickstart中。

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

只是一些更新的人有这个消息的麻烦,主要是因为只有第一个命令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致命错误:Uncaught LogicException: refresh token必须传入或设置为setAccessToken在/Library/WebServer/Documents/Sites/test/scripts/vendor/google/apiclient/src/google/Client.php:267

正在引用更新访问令牌(也称为刷新)方法:

$client->fetchAccessTokenWithRefreshToken($refreshTokenSaved);

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

凭证/calendar-php-quickstart.json

我发现了错误,由于如何accessToken数组打印出来时,我print_r:

[access_token] => Array([access_token] => xyz123[token_type] =>承载[expires_in] => 3600[refresh_token] => xsss112222[created] => 1511379484)

)

解决方案:

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

行前:

    $client->fetchAccessTokenWithRefreshToken($refreshToken);

当令牌在一小时内到期时,我可以根据需要刷新它。我认为本文的开发人员假设数组打印为:

数组([access_token] => xyz123[token_type] =>承载[expires_in] => 3600[refresh_token] => xsss112222[created] => 1511379484)

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

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

写入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;
}

Google已经更新了他们的PHP快速入门,用一个改进的方法来处理这个问题:

代码片段如下:

// 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');

一旦这样做了,在谷歌文档中给出的示例代码将工作。

// 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($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,它会工作得很好,并更新你的文件,希望它能解决一些问题。