如何使用Google API客户端刷新令牌?


91

我一直在使用Google Analytics(分析)API(V3),遇到了som错误。首先,所有设置均正确无误,并且可以使用我的测试帐户。但是,当我想从另一个个人资料ID(相同的Google Accont / GA帐户)获取数据时,出现403错误。奇怪的是,某些Google Analytics(分析)帐户中的数据会返回数据,而其他帐户会生成此错误。

我已经撤消了令牌并再次进行了身份验证,现在看来我可以从所有帐户中获取数据了。问题解决了?不。由于访问密钥将过期,因此我将再次遇到相同的问题。

如果我理解正确,可以使用resfreshToken获得新的authenticationTooken。

问题是,当我运行时:

$client->refreshToken(refresh_token_key) 

返回以下错误:

Error refreshing the OAuth2 token, message: '{ "error" : "invalid_grant" }'

我已经检查了refreshToken方法背后的代码,并将请求追溯到“ apiOAuth2.php”文件。所有参数均正确发送。grant_type在方法中硬编码为“ refresh_token”,因此我很难理解出了什么问题。参数数组如下所示:

Array ( [client_id] => *******-uqgau8uo1l96bd09eurdub26c9ftr2io.apps.googleusercontent.com [client_secret] => ******** [refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY [grant_type] => refresh_token )

步骤如下。

$client = new apiClient();
$client->setClientId($config['oauth2_client_id']);
$client->setClientSecret($config['oauth2_client_secret']);
$client->setRedirectUri($config['oauth2_redirect_uri']);
$client->setScopes('https://www.googleapis.com/auth/analytics.readonly');
$client->setState('offline');

$client->setAccessToken($config['token']); // The access JSON object.

$client->refreshToken($config['refreshToken']); // Will return error here

这是一个错误,还是我完全误解了某些东西?


不知道这是错误还是其他问题,但我目前正在使用原始的CURL http请求刷新访问令牌,并且工作正常。
gremo

Seorch ...你知道这个了吗?这里同样的问题。
布莱恩·范德布施

@gremo您可以在这里共享使用的原始CURL http请求吗?真的很有帮助。谢谢!
Silver Ringvee

Answers:


76

所以我终于想出了怎么做。基本思想是,您拥有首次请求身份验证时获得的令牌。该第一个令牌具有刷新令牌。一个小时后,第一个原始令牌将过期。一个小时后,您必须使用第一个令牌中的刷新令牌来获取新的可用令牌。您用于$client->refreshToken($refreshToken)检索新令牌。我将其称为“临时令牌”。您还需要存储此临时令牌,因为一小时后它也会过期,并请注意它没有与之关联的刷新令牌。为了获得新的临时令牌,您需要使用之前使用的方法,并使用第一个令牌的refreshtoken。我在下面附加了代码,这很丑陋,但是我在这方面是新的...

//pull token from database
$tokenquery="SELECT * FROM token WHERE type='original'";
$tokenresult = mysqli_query($cxn,$tokenquery);
if($tokenresult!=0)
{
    $tokenrow=mysqli_fetch_array($tokenresult);
    extract($tokenrow);
}
$time_created = json_decode($token)->created;
$t=time();
$timediff=$t-$time_created;
echo $timediff."<br>";
$refreshToken= json_decode($token)->refresh_token;


//start google client note:
$client = new Google_Client();
$client->setApplicationName('');
$client->setScopes(array());
$client->setClientId('');
$client->setClientSecret('');
$client->setRedirectUri('');
$client->setAccessType('offline');
$client->setDeveloperKey('');

//resets token if expired
if(($timediff>3600)&&($token!=''))
{
    echo $refreshToken."</br>";
    $refreshquery="SELECT * FROM token WHERE type='refresh'";
    $refreshresult = mysqli_query($cxn,$refreshquery);
    //if a refresh token is in there...
    if($refreshresult!=0)
    {
        $refreshrow=mysqli_fetch_array($refreshresult);
        extract($refreshrow);
        $refresh_created = json_decode($token)->created;
        $refreshtimediff=$t-$refresh_created;
        echo "Refresh Time Diff: ".$refreshtimediff."</br>";
        //if refresh token is expired
        if($refreshtimediff>3600)
        {
            $client->refreshToken($refreshToken);
        $newtoken=$client->getAccessToken();
        echo $newtoken."</br>";
        $tokenupdate="UPDATE token SET token='$newtoken' WHERE type='refresh'";
        mysqli_query($cxn,$tokenupdate);
        $token=$newtoken;
        echo "refreshed again";
        }
        //if the refresh token hasn't expired, set token as the refresh token
        else
        {
        $client->setAccessToken($token);
           echo "use refreshed token but not time yet";
        }
    }
    //if a refresh token isn't in there...
    else
    {
        $client->refreshToken($refreshToken);
        $newtoken=$client->getAccessToken();
        echo $newtoken."</br>";
        $tokenupdate="INSERT INTO token (type,token) VALUES ('refresh','$newtoken')";
        mysqli_query($cxn,$tokenupdate);
        $token=$newtoken;
        echo "refreshed for first time";
    }      
}

//if token is still good.
if(($timediff<3600)&&($token!=''))
{
    $client->setAccessToken($token);
}

$service = new Google_DfareportingService($client);

52
而不是检查3600秒,您应该使用$ client-> isAccessTokenExpired()
Gaurav Gupta 2014年

2
小更新。在最新版本中,当您请求刷新令牌时,现在返回的新访问令牌带有新的刷新令牌。因此,从本质上讲,您可以使用更新的json令牌替换以前的json令牌,并且不再需要保留初始访问令牌。。
Skidadon '16

1
请注意,它$client->isAccessTokenExpired()仍将仅检查本地持有的时间,以查看其是否认为令牌已过期。令牌可能仍已过期,并且本地应用程序仅在尝试使用令牌时才真正知道。在这种情况下,API客户端将返回异常,并且不会自动刷新令牌。
杰森

44

问题出在刷新令牌中:

[refresh_token] => 1\/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY

当带有'/'gets 的字符串时,将使用进行json encoded转义'\',因此需要将其删除。

您的情况下的刷新令牌应为:

1/lov250YQTMCC9LRQbE6yMv-FiX_Offo79UXimV8kvwY

我假设您已经完成的工作是打印了Google发送回的json字符串,并将令牌复制并粘贴到了代码中,因为如果您 json_decode这样做,它将'\'为您正确删除!


1
精彩的提及,令我开心!节省时间!
Mircea Sandu 2014年

你救了我的一天!
Truong Dang

我希望我可以投票100次。在尝试了使令牌完全可用的所有方法之后,盯着“不良授权”消息看了好几个小时,然后我打算用键盘在墙上打孔。欺骗Google男人,为什么要使用斜杠,为什么?
阿斯克曼

18

这是设置令牌的代码段,在此之前,请确保将访问类型设置为离线

if (isset($_GET['code'])) {
  $client->authenticate();
  $_SESSION['access_token'] = $client->getAccessToken();
}

刷新令牌

$google_token= json_decode($_SESSION['access_token']);
$client->refreshToken($google_token->refresh_token);

这将刷新您的令牌,因此您必须在会话中对其进行更新

 $_SESSION['access_token']= $client->getAccessToken()

1
您用了这一天:)非常感谢,比我想象的要简单得多,因为我一直花费大量时间无所事事:D
TB Ygg 2014年

16

访问类型应设置为offlinestate是您设置供自己使用而不是API使用的变量。

确保您具有最新版本的客户端库,然后添加:

$client->setAccessType('offline');

有关参数的说明,请参见形成URL


谢谢你 我已经下载了最新版本,并撤消了我帐户的应用访问权限。然后,我又授予访问一次权限,并存储了accessToken和refreshToken。问题是,即使省略了setAccessType,我也总是得到了refreshToken。无论如何,当我运行$ client-> refreshToken(refresh-token-key)时,我仍然收到“ invalid_grant”错误。我已经检查了auth-url,它默认为“ force”。如果我将其更改为“ auto”并运行authenticate方法,则不会重定向,因为我已经授予访问权限。但是响应是一个没有刷新的accessToken。有任何想法吗?
seorch.me 2012年

@ seorch.me听起来很疯狂,但是是否可能必须设置一个新$client$client = new apiClient();)才能使用刷新令牌?
jk。

1
您必须设置@ seorch.me $client->setApprovalPrompt('force')$client->setAccessType('offline')在授权期间获取新的刷新令牌。在不强迫用户批准访问范围的前提下,Google假设您将继续使用旧的刷新令牌。
杰森

14

@ uri-weg发布的答案对我有用,但是由于我不十分清楚他的解释,所以我稍微改一下。

在第一个访问权限序列中,在回调中,当您到达接收验证码的位置时,还必须保存访问令牌和刷新令牌

原因是Google api仅在提示输入访问权限时才向您发送带有刷新令牌的访问令牌。下次访问令牌将被发送而没有任何刷新令牌(除非您使用该approval_prompt=force选项)。

您第一次收到的刷新令牌在用户撤销访问权限之前一直保持有效。

在简单的php中,回调序列的示例为:

// init client
// ...

$authCode = $_GET['code'];
$accessToken = $client->authenticate($authCode);
// $accessToken needs to be serialized as json
$this->saveAccessToken(json_encode($accessToken));
$this->saveRefreshToken($accessToken['refresh_token']);

然后,在简单化的php中,连接顺序为:

// init client
// ...

$accessToken = $this->loadAccessToken();
// setAccessToken() expects json
$client->setAccessToken($accessToken);

if ($client->isAccessTokenExpired()) {
    // reuse the same refresh token
    $client->refreshToken($this->loadRefreshToken());
    // save the new access token (which comes without any refresh token)
    $this->saveAccessToken($client->getAccessToken());
}

完美,工作很多。我唯一要说的是,您应该解释您需要传递json对象,而不仅仅是令牌作为字符串。
奥利弗·贝叶斯-谢尔顿

@ OliverBayes-Shelton嗨。谢谢。我认为// setAccessToken() expects json足够了。还是代码的另一部分?
Daishi

这对我来说很有用,但是您是否知道此代码是否可以处理由于超过50次令牌刷新限制而导致令牌过期的情况?有关“令牌过期”的详细信息,可以在以下位置找到:developers.google.com/identity/protocols/OAuth2#expiration
比约恩

似乎最新的2.0版本现在正在返回访问令牌数组中的刷新令牌。这意味着保存访问令牌也将保存刷新令牌,因为包括了刷新令牌。为了响应刷新令牌到期,我想必须对此进行测试并进行明确的处理-请记住50个限制是“每个用户每个客户”,即50个每个客户端,因此您不太可能达到该限制,尤其是在您使用包含范围合并令牌。
Brian C

8

这是我在项目中使用的代码,它工作正常:

public function getClient(){
    $client = new Google_Client();
    $client->setApplicationName(APPNAME);       // app name
    $client->setClientId(CLIENTID);             // client id
    $client->setClientSecret(CLIENTSECRET);     // client secret 
    $client->setRedirectUri(REDIRECT_URI);      // redirect uri
    $client->setApprovalPrompt('auto');

    $client->setAccessType('offline');         // generates refresh token

    $token = $_COOKIE['ACCESSTOKEN'];          // fetch from cookie

    // if token is present in cookie
    if($token){
        // use the same token
        $client->setAccessToken($token);
    }

    // this line gets the new token if the cookie token was not present
    // otherwise, the same cookie token
    $token = $client->getAccessToken();

    if($client->isAccessTokenExpired()){  // if token expired
        $refreshToken = json_decode($token)->refresh_token;

        // refresh the token
        $client->refreshToken($refreshToken);
    }

    return $client;
}

6

有同样的问题;我的脚本昨天工作了,出于某种奇怪的原因,今天没有。没有变化。

显然这是因为我的系统时钟关闭了2.5(!!)秒,与NTP同步修复了它。

另请参阅:https : //code.google.com/p/google-api-php-client/wiki/OAuth2#Solving_invalid_grant_errors


那个答案对我很有帮助,伙计。你可能节省了我很多时间。很多!谢谢!我只是sudo apt-get install ntp在Debian机器上执行安装NTP。它同步了时钟,问题得以解决。
SzymonSadło17年

4

仅供参考:如果您有刷新令牌,则3.0 Google Analytics(分析)API会在刷新令牌时自动刷新访问令牌,因此您的脚本将永远不需要refreshToken

(请参阅中的Sign功能auth/apiOAuth2.php


“自动刷新”意味着我只需要询问getAccessToken()就可以得到一个刷新的回传?但是我必须首先从数据库中设置刷新令牌,对吗?否则,刷新将在没有刷新令牌的情况下起作用,并且我认为这不会起作用
ninsky

4

有时我无法通过使用生成刷新令牌 $client->setAccessType ("offline");

试试这个:

$client->setAccessType ("offline");
$client->setApprovalPrompt ("force"); 

更具体地说,您的首次授权中似乎包含了“刷新”令牌。如果您保存然后使用它,我相信(根据他人的说法,未经验证)刷新令牌将继续返回。Doco现在还说,如果他们有刷新令牌,他们将自动刷新访问令牌,这仅是安全地管理刷新令牌的问题。setApprovalPrompt('force')确实强制随后发出刷新令牌;没有它,您将不会再获得另一个。
Brian C

2

我在当前版本的Google API中通过smartcode使用了该示例,但该示例无法正常工作。我认为他的API太过时了。

因此,我只是根据一个API示例编写了自己的版本...它以字符串形式输出访问令牌,请求令牌,令牌类型,ID令牌,到期时间和创建时间

如果您的客户端凭据和开发人员密钥正确,则此代码应立即可用。

<?php
// Call set_include_path() as needed to point to your client library.
require_once 'google-api-php-client/src/Google_Client.php';
require_once 'google-api-php-client/src/contrib/Google_Oauth2Service.php';
session_start();

$client = new Google_Client();
$client->setApplicationName("Get Token");
// Visit https://code.google.com/apis/console?api=plus to generate your
// oauth2_client_id, oauth2_client_secret, and to register your oauth2_redirect_uri.
$oauth2 = new Google_Oauth2Service($client);

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

if (isset($_SESSION['token'])) {
    $client->setAccessToken($_SESSION['token']);
}

if (isset($_REQUEST['logout'])) {
    unset($_SESSION['token']);
    $client->revokeToken();
}
?>
<!doctype html>
<html>
    <head><meta charset="utf-8"></head>
    <body>
        <header><h1>Get Token</h1></header>
        <?php
        if ($client->getAccessToken()) {
            $_SESSION['token'] = $client->getAccessToken();
            $token = json_decode($_SESSION['token']);
            echo "Access Token = " . $token->access_token . '<br/>';
            echo "Refresh Token = " . $token->refresh_token . '<br/>';
            echo "Token type = " . $token->token_type . '<br/>';
            echo "Expires in = " . $token->expires_in . '<br/>';
            echo "ID Token = " . $token->id_token . '<br/>';
            echo "Created = " . $token->created . '<br/>';
            echo "<a class='logout' href='?logout'>Logout</a>";
        } else {
            $authUrl = $client->createAuthUrl();
            print "<a class='login' href='$authUrl'>Connect Me!</a>";
        }
        ?>
    </body>
</html>

1
拜托,你能解释我为什么这一行:$redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];。为什么要重定向到同一页面?这有必要吗?
Tropicalista

@Tropicalista:本身不必重新加载页面,但这是通常实现身份验证流程的方式。
John Slegers 2014年

但如果访问令牌已过期,则您没有使用刷新令牌来获取新的访问令牌。
apadana's

1

我在使用google / google-api-php-client v2.0.0-RC7时遇到了相同的问题, 搜索了1个小时后,我使用json_encode 这样解决了此问题:

    if ($client->isAccessTokenExpired()) {
        $newToken = json_decode(json_encode($client->getAccessToken()));
        $client->refreshToken($newToken->refresh_token);
        file_put_contents(storage_path('app/client_id.txt'), json_encode($client->getAccessToken()));
    }

1

这在这里效果很好,也许可以帮助任何人:

index.php

session_start();

require_once __DIR__.'/client.php';

if(!isset($obj->error) && isset($_SESSION['access_token']) && $_SESSION['access_token'] && isset($obj->expires_in)) {
?>
<!DOCTYPE html>
<html>
<head>
<title>Google API Token Test</title>
<meta charset='utf-8' />
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script>
search('Music Mix 2010');
function search(q) {
    $.ajax({
        type: 'GET',
        url: 'action.php?q='+q,
        success: function(data) {
            if(data == 'refresh') location.reload();
            else $('#response').html(JSON.stringify(JSON.parse(data)));
        }
    });
}
</script>
</head>
<body>
<div id="response"></div>
</body>
</html>
<?php
}
else header('Location: '.filter_var('https://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']).'/oauth2callback.php', FILTER_SANITIZE_URL));
?>

oauth2callback.php

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

session_start();

$client = new Google_Client();
$client->setAuthConfigFile('auth.json');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->setRedirectUri('https://'.filter_var($_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF'], FILTER_SANITIZE_URL));
$client->addScope(Google_Service_YouTube::YOUTUBE_FORCE_SSL);

if(isset($_GET['code']) && $_GET['code']) {
    $client->authenticate(filter_var($_GET['code'], FILTER_SANITIZE_STRING));
    $_SESSION['access_token'] = $client->getAccessToken();
    $_SESSION['refresh_token'] = $_SESSION['access_token']['refresh_token'];
    setcookie('refresh_token', $_SESSION['refresh_token'], time()+60*60*24*180, '/', filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL), true, true);
    header('Location: '.filter_var('https://'.$_SERVER['HTTP_HOST'].dirname($_SERVER['PHP_SELF']), FILTER_SANITIZE_URL));
    exit();
}
else header('Location: '.filter_var($client->createAuthUrl(), FILTER_SANITIZE_URL));
exit();

?>

client.php

// https://developers.google.com/api-client-library/php/start/installation
require_once __DIR__.'/vendor/autoload.php';

$client = new Google_Client();
$client->setAuthConfig('auth.json');
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->addScope(Google_Service_YouTube::YOUTUBE_FORCE_SSL);

// Delete Cookie Token
#setcookie('refresh_token', @$_SESSION['refresh_token'], time()-1, '/', filter_var($_SERVER['HTTP_HOST'], FILTER_SANITIZE_URL), true, true);

// Delete Session Token
#unset($_SESSION['refresh_token']);

if(isset($_SESSION['refresh_token']) && $_SESSION['refresh_token']) {
    $client->refreshToken($_SESSION['refresh_token']);
    $_SESSION['access_token'] = $client->getAccessToken();
}
elseif(isset($_COOKIE['refresh_token']) && $_COOKIE['refresh_token']) {
    $client->refreshToken($_COOKIE['refresh_token']);
    $_SESSION['access_token'] = $client->getAccessToken();
}

$url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.urlencode(@$_SESSION['access_token']['access_token']);
$curl_handle = curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $url);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, 2);
curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_USERAGENT, 'Google API Token Test');
$json = curl_exec($curl_handle);
curl_close($curl_handle);

$obj = json_decode($json);

?>

action.php

session_start();

require_once __DIR__.'/client.php';

if(isset($obj->error)) {
    echo 'refresh';
    exit();
}
elseif(isset($_SESSION['access_token']) && $_SESSION['access_token'] && isset($obj->expires_in) && isset($_GET['q']) && !empty($_GET['q'])) {
    $client->setAccessToken($_SESSION['access_token']);
    $service = new Google_Service_YouTube($client);
    $response = $service->search->listSearch('snippet', array('q' => filter_input(INPUT_GET, 'q', FILTER_SANITIZE_SPECIAL_CHARS), 'maxResults' => '1', 'type' => 'video'));
    echo json_encode($response['modelData']);
    exit();
}
?>

1

自最初发布此问题以来,Google进行了一些更改。

这是我目前的工作示例。

    public function update_token($token){

    try {

        $client = new Google_Client();
        $client->setAccessType("offline"); 
        $client->setAuthConfig(APPPATH . 'vendor' . DIRECTORY_SEPARATOR . 'google' . DIRECTORY_SEPARATOR . 'client_secrets.json');  
        $client->setIncludeGrantedScopes(true); 
        $client->addScope(Google_Service_Calendar::CALENDAR); 
        $client->setAccessToken($token);

        if ($client->isAccessTokenExpired()) {
            $refresh_token = $client->getRefreshToken();
            if(!empty($refresh_token)){
                $client->fetchAccessTokenWithRefreshToken($refresh_token);      
                $token = $client->getAccessToken();
                $token['refresh_token'] = json_decode($refresh_token);
                $token = json_encode($token);
            }
        }

        return $token;

    } catch (Exception $e) { 
        $error = json_decode($e->getMessage());
        if(isset($error->error->message)){
            log_message('error', $error->error->message);
        }
    }


}

1

我使用google-api-php-client v2.2.2,fetchAccessTokenWithRefreshToken();如果函数调用不带参数,则会得到一个新令牌,它会返回更新的访问令牌,并且刷新后的令牌不会丢失。

if ($client->getAccessToken() && $client->isAccessTokenExpired()) {
    $new_token=$client->fetchAccessTokenWithRefreshToken();
    $token_data = $client->verifyIdToken();
}    

1

在初始授权请求期间,您需要将访问令牌作为json字符串保存到文件或数据库中,并将访问类型设置为离线 $client->setAccessType("offline")

然后,在后续的api请求期间,从文件或数据库中获取访问令牌并将其传递给客户端:

$accessToken = json_decode($row['token'], true);
$client->setAccessToken($accessToken);

现在,您需要检查令牌是否已过期:

if ($client->isAccessTokenExpired()) {
    // access token has expired, use the refresh token to obtain a new one
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    // save the new token to file or db
    // ...json_encode($client->getAccessToken())

fetchAccessTokenWithRefreshToken()功能将为您完成工作并提供新的访问令牌,并将其保存回您的文件或数据库中。



-1

使用以下代码片段获取刷新令牌

    <?php

    require_once 'src/apiClient.php';
    require_once 'src/contrib/apiTasksService.php';

    $client = new apiClient();
    $client->setAccessType('offline');
    $tasksService = new apiTasksService($client);

    $auth = $client->authenticate();
    $token = $client->getAccessToken();
    // the refresh token
    $refresh_token = $token['refresh_token'];
    ?>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.