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->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 JavaScript 客户端库的 Google Contacts API