没有接收谷歌OAuth刷新令牌

我想从谷歌获取访问令牌。谷歌API说表示要获得访问令牌,将代码和其他参数发送到令牌生成页面,并且响应将是一个JSON对象,如:

{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}

但是,我没有收到刷新令牌。我的回答是:

{
"access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
167515 次浏览

refresh_token只在用户第一次授权时提供。后续的授权,比如测试OAuth2集成时所做的授权,将不会再次返回refresh_token。:)

  1. 进入显示可以访问您帐户的应用程序的页面: 李https://myaccount.google.com/u/0/permissions。< / >
  2. 在第三方应用程序菜单下,选择您的应用程序。
  3. 单击“删除访问”,然后单击“确定”确认
  4. 你的下一个OAuth2请求将返回一个refresh_token(前提是它还包括'access_type=offline'查询参数。

或者,你可以将查询参数prompt=consent&access_type=offline添加到OAuth重定向(参见谷歌的Web服务器应用的OAuth 2.0页面)。

这将提示用户再次授权应用程序,并始终返回refresh_token

为了获得刷新令牌,你必须同时添加approval_prompt=forceaccess_type="offline" 如果您正在使用谷歌提供的java客户端,它将看起来像这样:

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
.build();


AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
.setApprovalPrompt("force")
.setAccessType("offline");

对我来说,我正在尝试谷歌提供的CalendarSampleServlet。1小时后,access_key超时,并重定向到401页。我尝试了以上所有的方法,但都不起作用。最后,在检查“AbstractAuthorizationCodeServlet”的源代码时,我可以看到,如果存在凭据,重定向将被禁用,但理想情况下,它应该检查refresh token!=null。我将下面的代码添加到CalendarSampleServlet,之后它就工作了。在经历了这么多小时的挫折后,真是松了一口气。感谢上帝。

if (credential.getRefreshToken() == null) {
AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
authorizationUrl.setRedirectUri(getRedirectUri(req));
onAuthorization(req, resp, authorizationUrl);
credential = null;
}

我找了一个漫长的夜晚,这个很管用:

修改了admin-sdk中的user-example.php

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";

然后你得到重定向url的代码 使用代码进行身份验证并获得刷新令牌

$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();

你现在应该把它存储;)

当你的accesskey超时就做

$client->refreshToken($theRefreshTokenYouHadStored);

这给我带来了一些困惑,所以我想分享一下我艰难的经历:

当你使用access_type=offlineapproval_prompt=force参数请求访问时,你应该收到一个访问令牌和一个刷新令牌。访问令牌在您收到它后不久就会过期,您需要刷新它。

您正确地请求获得一个新的访问令牌,并收到了具有新的访问令牌的响应。我也困惑的事实,我没有得到一个新的刷新令牌。然而,这就是它的意义所在,因为你可以反复使用相同的刷新令牌。

我认为其他一些答案假设你想要获得一个新的刷新令牌出于某种原因,并建议你重新授权用户,但实际上,你不需要这样做,因为你拥有的刷新令牌将一直工作,直到用户撤销。

Rich Sutton的回答终于为我工作了,在我意识到添加access_type=offline是在前端客户端对授权代码的请求上完成的,是将该代码交换为access_token的后端请求。我已经在他的回答和这个链接在谷歌中添加了一个注释,以获得关于刷新令牌的更多信息。

附注:如果你正在使用Satellizer, 下面是如何将该选项添加到$authProvider。AngularJS中的谷歌

设置这个将导致刷新令牌每次都被发送:

$client->setApprovalPrompt('force');

下面给出一个例子(php):

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile");
$client->setAccessType('offline');
$client->setApprovalPrompt('force');

现在谷歌已经拒绝了这些参数在我的请求(access_type,提示)…:(并且根本没有“撤销访问权限”按钮。我很沮丧,因为回到我的refresh_token lol

< p >更新: 我在这里找到了答案:D,您可以通过请求获得刷新令牌 https://developers.google.com/identity/protocols/OAuth2WebServer < / p >

curl -H "Content-type:application/x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token={牌}< / p >

令牌可以是访问令牌,也可以是刷新令牌。如果令牌是访问令牌,并且它有相应的刷新令牌,则刷新令牌也将被撤销。

如果成功地处理了撤销,则响应的状态码为200。对于错误条件,将返回状态代码400和错误代码。

    #!/usr/bin/env perl


use strict;
use warnings;
use 5.010_000;
use utf8;
binmode STDOUT, ":encoding(utf8)";


use Text::CSV_XS;
use FindBin;
use lib $FindBin::Bin . '/../lib';
use Net::Google::Spreadsheets::V4;


use Net::Google::DataAPI::Auth::OAuth2;


use lib 'lib';
use Term::Prompt;
use Net::Google::DataAPI::Auth::OAuth2;
use Net::Google::Spreadsheets;
use Data::Printer ;




my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => $ENV{CLIENT_ID},
client_secret => $ENV{CLIENT_SECRET},
scope => ['https://www.googleapis.com/auth/spreadsheets'],
);
my $url = $oauth2->authorize_url();
# system("open '$url'");
print "go to the following url with your browser \n" ;
print "$url\n" ;
my $code = prompt('x', 'paste code: ', '', '');
my $objToken = $oauth2->get_access_token($code);


my $refresh_token = $objToken->refresh_token() ;


print "my refresh token is : \n" ;
# debug p($refresh_token ) ;
p ( $objToken ) ;




my $gs = Net::Google::Spreadsheets::V4->new(
client_id      => $ENV{CLIENT_ID}
, client_secret  => $ENV{CLIENT_SECRET}
, refresh_token  => $refresh_token
, spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
);


my($content, $res);


my $title = 'My foobar sheet';


my $sheet = $gs->get_sheet(title => $title);


# create a sheet if does not exit
unless ($sheet) {
($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => [
{
addSheet => {
properties => {
title => $title,
index => 0,
},
},
},
],
},
);


$sheet = $content->{replies}[0]{addSheet};
}


my $sheet_prop = $sheet->{properties};


# clear all cells
$gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});


# import data
my @requests = ();
my $idx = 0;


my @rows = (
[qw(name age favorite)], # header
[qw(tarou 31 curry)],
[qw(jirou 18 gyoza)],
[qw(saburou 27 ramen)],
);


for my $row (@rows) {
push @requests, {
pasteData => {
coordinate => {
sheetId     => $sheet_prop->{sheetId},
rowIndex    => $idx++,
columnIndex => 0,
},
data => $gs->to_csv(@$row),
type => 'PASTE_NORMAL',
delimiter => ',',
},
};
}


# format a header row
push @requests, {
repeatCell => {
range => {
sheetId       => $sheet_prop->{sheetId},
startRowIndex => 0,
endRowIndex   => 1,
},
cell => {
userEnteredFormat => {
backgroundColor => {
red   => 0.0,
green => 0.0,
blue  => 0.0,
},
horizontalAlignment => 'CENTER',
textFormat => {
foregroundColor => {
red   => 1.0,
green => 1.0,
blue  => 1.0
},
bold => \1,
},
},
},
fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
},
};


($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => \@requests,
},
);


exit;


#Google Sheets API, v4


# Scopes
# https://www.googleapis.com/auth/drive   View and manage the files in your Google D# # i# rive
# https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
# https://www.googleapis.com/auth/drive.readonly   View the files in your Google Drive
# https://www.googleapis.com/auth/spreadsheets  View and manage your spreadsheets in Google Drive
# https://www.googleapis.com/auth/spreadsheets.readonly  View your Google Spreadsheets

为了获得refresh_token,你需要在OAuth请求URL中包含access_type=offline。当用户第一次验证时,你会得到一个非空的refresh_token和一个过期的access_token

如果遇到这样的情况,用户可能会重新验证您已经拥有身份验证令牌的帐户(如上面提到的@SsjCosty),则需要从谷歌获取令牌用于哪个帐户的信息。为此,将profile添加到作用域。使用OAuth2 Ruby宝石,你的最终请求可能看起来像这样:

client = OAuth2::Client.new(
ENV["GOOGLE_CLIENT_ID"],
ENV["GOOGLE_CLIENT_SECRET"],
authorize_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://accounts.google.com/o/oauth2/token"
)


# Configure authorization url
client.authorize_url(
scope: "https://www.googleapis.com/auth/analytics.readonly profile",
redirect_uri: callback_url,
access_type: "offline",
prompt: "select_account"
)

注意,作用域有两个空格分隔的条目,一个用于对谷歌Analytics的只读访问,另一个只是profile,这是一个OpenID连接标准。

这将导致谷歌在get_token响应中提供一个名为id_token的附加属性。要从谷歌文档中的id_token, 看看这个页面中获取信息。有一些google提供的库可以为你验证和“解码”它(我使用Ruby google-id-token宝石)。解析后,sub参数实际上是唯一的谷歌帐户ID。

值得注意的是,如果你改变作用域,你将为已经通过原始作用域身份验证的用户再次获得一个刷新令牌。这很有用,如果你已经有一群用户,不想让他们都在谷歌中取消应用程序的认证。

哦,最后一个注意:你不需要 prompt=select_account,但它是有用的,如果你有一个情况,你的用户可能想要验证与多个谷歌帐户(即,你不使用这个登录/身份验证)。

使用离线访问提示:同意对我来说工作得很好:

   auth2 = gapi.auth2.init({
client_id: '{cliend_id}'
});


auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback);
我的解决方案有点奇怪,我在网上找到的所有解决方案都试过了,但一无所获。令人惊讶的是,这竟然起作用了:删除凭据。Json,刷新,重新在你的账户中保存你的应用。新的证书。Json文件将有刷新令牌。把这个文件备份到某个地方。 然后继续使用您的应用程序,直到刷新令牌错误再次出现。删除凭证。Json文件,现在只有一个错误消息(这发生在我的情况下),然后粘贴您的旧凭据文件在文件夹,它完成! 这是一个星期以来,我这样做没有更多的问题。< / p >

为了在每次验证时获得新的refresh_token,在仪表板中创建的OAuth 2.0凭据类型应该是“Other”。同样如上所述,在生成authURL时应该使用access_type='offline'选项。

当使用类型为“Web application”的凭据时,prompt/approval_prompt变量的组合将不起作用——您仍然只能在第一个请求上获得refresh_token。

1.如何获得'refresh_token' ?

解决方案: access_type='offline'选项应该在生成authURL时使用。 来源:为Web服务器应用程序使用OAuth 2.0

2.但即使有'access_type=offline',我没有得到'refresh_token' ?

解决方案:请注意,你只会在第一次请求时得到它,所以如果你将它存储在某个地方,并且有一个条款在你的代码中覆盖这个,当获得新的access_token在前一次过期后,那么请确保不要覆盖这个值。

From谷歌Auth Doc:(this value = access_type)

该值指示谷歌授权服务器返回a 刷新令牌和访问令牌,第一次您的应用程序

.将授权码交换为令牌

如果你再次需要'refresh_token',那么你需要按照Rich Sutton的回答中所写的步骤删除应用程序的访问。

access_type=offline添加到授权谷歌授权URL对我来说很有用。我使用Java和Spring框架。

下面是创建客户端注册的代码:

return CommonOAuth2Provider.GOOGLE
.getBuilder(client)
.scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
.clientId(clientId)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientSecret(clientSecret)
.build();

这里重要的部分是授权URI, ?access_type=offline被附加到该URI。

我正在使用nodejs客户端访问私有数据

解决方案是在oAuth2Client.generateAuthUrl函数的设置对象中添加值为同意promp属性。 下面是我的代码:

const getNewToken = (oAuth2Client, callback) => {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: SCOPES,
})
console.log('Authorize this app by visiting this url:', authUrl)
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
rl.question('Enter the code from that page here: ', (code) => {
rl.close()
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error while trying to retrieve access token', err)
oAuth2Client.setCredentials(token)
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err)
console.log('Token stored to', TOKEN_PATH)
})
callback(oAuth2Client)
})
})
}

你可以使用在线参数提取器来获取生成令牌的代码:

在线参数提取器

以下是谷歌官方文档的完整代码:

https://developers.google.com/sheets/api/quickstart/nodejs

我希望这些信息对你有用

我想为那些遇到这个问题的沮丧灵魂补充一点关于这个主题的信息。为离线应用程序获取刷新令牌的关键是确保你正在呈现同意屏幕refresh_token仅在用户通过单击“允许”授予授权后立即返回。

enter image description here

在我在开发环境中做了一些测试后,这个问题出现在我身上(我怀疑许多其他人),因此在一个给定的帐户上已经批准了我的申请。然后,我转移到生产环境,尝试使用一个已经获得授权的帐户再次进行身份验证。在这种情况下,同意屏幕将不会再次出现,并且api 不会返回一个新的刷新令牌. c。要实现此功能,您必须通过以下方式强制再次出现同意屏幕:

prompt=consent

approval_prompt=force

任何一个都可以,但你不应该同时使用。从2021年开始,我建议使用prompt=consent因为它取代了旧的参数approval_prompt,在一些api版本中,后者实际上是坏的(https://github.com/googleapis/oauth2client/issues/453)。此外,prompt是一个空格分隔的列表,所以如果你想要两者都设置为prompt=select_account%20consent,你可以将它设置为prompt=select_account%20consent

当然你还需要:

access_type=offline

更多阅读:

  • 要使用postman获取刷新令牌,下面是一个配置示例

enter image description here

  • 预期响应

enter image description here