我想从 Google 获取访问令牌。 The Google API says 获取访问令牌,将代码和其他参数发送到令牌生成页面,响应将是一个 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
}
refresh_token
仅在用户首次授权时提供。后续授权(例如您在测试 OAuth2 集成时进行的授权)不会再次返回 refresh_token
。 :)
转到显示有权访问您的帐户的应用程序的页面:https://myaccount.google.com/u/0/permissions。在第三方应用程序菜单下,选择您的应用程序。单击删除访问,然后单击确定以确认您发出的下一个 OAuth2 请求将返回一个 refresh_token(前提是它还包括“access_type=offline”查询参数。
或者,您可以将查询参数 prompt=consent&access_type=offline
添加到 OAuth 重定向(请参阅 Google 的 OAuth 2.0 for Web Server Applications 页面)。
这将提示用户再次授权应用程序并始终返回 refresh_token
。
为了获得刷新令牌,您必须同时添加 approval_prompt=force
和 access_type="offline"
如果您使用的是 Google 提供的 java 客户端,它将如下所示:
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
.build();
AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
.setApprovalPrompt("force")
.setAccessType("offline");
我搜索了一个漫长的夜晚,这就是诀窍:
从 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();
你现在应该把它存起来;)
当您的访问密钥超时时
$client->refreshToken($theRefreshTokenYouHadStored);
我想为那些遇到此问题的沮丧灵魂添加有关此主题的更多信息。为离线应用获取刷新令牌的关键是确保您展示的是同意屏幕。 refresh_token
仅在用户通过单击“允许”授予授权后立即返回。
https://i.stack.imgur.com/fxLiZ.png
在我在开发环境中进行了一些测试并因此已经在给定帐户上授权了我的应用程序之后,我(我怀疑还有许多其他问题)出现了这个问题。然后我转到生产环境并尝试使用已授权的帐户再次进行身份验证。在这种情况下,同意屏幕不会再次出现,api 也不会返回新的刷新令牌。要完成这项工作,您必须通过以下任一方式强制同意屏幕再次出现:
prompt=consent
或者
approval_prompt=force
任何一个都可以,但你不应该同时使用。 自 2021 年起,我建议使用 prompt=consent
,因为它替换了旧参数 approval_prompt
,并且在某些 api 版本中,后者实际上已损坏 (https://github.com/googleapis/oauth2client/issues/453)。此外,prompt
是一个以空格分隔的列表,因此您可以将两者都设置为 prompt=select_account%20consent
。
当然你还需要:
access_type=offline
补充阅读:
文档:https://developers.google.com/identity/protocols/oauth2/web-server#request-parameter-prompt
文档:https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent
关于这个问题的讨论:https://github.com/googleapis/google-api-python-client/issues/213
oauth2.ApprovalForce
AuthCodeOption: pkg.go.dev/golang.org/x/oauth2#AuthCodeOption 进行设置
这让我有些困惑,所以我想我会分享一下我辛苦学习的东西:
当您使用 access_type=offline
和 approval_prompt=force
参数请求访问时,您应该同时收到 access 令牌和 refresh 令牌。 access 令牌在您收到后很快就会过期,您需要刷新它。
您正确地发出了获取新访问令牌的请求,并收到了包含新访问令牌的响应。我也对我没有获得新的刷新令牌这一事实感到困惑。但是,这就是它的本意,因为您可以一遍又一遍地使用相同的刷新令牌。
我认为其他一些答案假设您出于某种原因想要获得一个新的刷新令牌,并建议您重新授权用户,但实际上,您不需要这样做,因为您拥有的刷新令牌将一直工作到被用户撤销。
Rich Sutton's answer 终于为我工作了,在我意识到添加 access_type=offline
是在 前端 客户端对授权码的请求上完成的,不是 交换的后端请求access_token 的代码。我在他的回答中添加了评论,并在 this link at Google 中添加了有关刷新令牌的更多信息。
PS 如果您使用的是卫星发射器,here is how to add that option to the $authProvider.google in AngularJS。
为了获得 refresh_token
,您需要在 OAuth 请求 URL 中包含 access_type=offline
。当用户第一次进行身份验证时,您将返回一个非零 refresh_token
以及一个过期的 access_token
。
如果您遇到用户可能重新验证您已经拥有身份验证令牌的帐户的情况(如上面提到的@SsjCosty),您需要从 Google 获取有关该令牌用于哪个帐户的信息。为此,请将 profile
添加到您的范围。使用 OAuth2 Ruby gem,您的最终请求可能如下所示:
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"
)
请注意,范围有两个以空格分隔的条目,一个用于对 Google Analytics 的只读访问,另一个只是 profile
,它是 OpenID Connect 标准。
这将导致 Google 在 get_token
响应中提供一个名为 id_token
的附加属性。要从 id_token 中获取信息,请参阅 Google 文档中的 check out this page。有一些 Google 提供的库可以为您验证和“解码”它(我使用了 Ruby google-id-token gem)。解析后,sub
参数实际上就是唯一的 Google 帐户 ID。
值得注意的是,如果您更改范围,您将再次为已经使用原始范围进行身份验证的用户取回刷新令牌。例如,如果您已经有很多用户并且不想让他们都在 Google 中取消对应用程序的身份验证,这很有用。
哦,最后一点:您需要 prompt=select_account
,但如果您的用户可能希望使用多个 Google 帐户进行身份验证(即,您是不使用它进行登录/身份验证)。
1.如何获得'refresh_token'?
解决方案: 生成 authURL 时应使用 access_type='offline' 选项。来源:Using OAuth 2.0 for Web Server Applications
2. 但即使使用“access_type=offline”,我也没有得到“refresh_token”?
解决方案:请注意,您只会在第一次请求时获得它,所以如果您将它存储在某个地方并且在您的代码中规定在先前过期后获取新的 access_token 时覆盖它,那么请确保不要覆盖这个值。
来自 Google Auth Doc:(此值 = access_type)
此值指示 Google 授权服务器在您的应用程序第一次为令牌交换授权代码时返回刷新令牌和访问令牌。
如果您再次需要“refresh_token”,则需要按照 Rich Sutton's answer 中编写的步骤删除您的应用程序的访问权限。
我正在使用 nodejs 客户端访问私有数据
解决方案是将带有值同意的提示属性添加到 oAuth2Client.generateAuthUrl 函数中的设置对象。这是我的代码:
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
我希望这些信息有用
设置此项将导致每次发送刷新令牌:
$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');
对我来说,我正在试用 Google 提供的 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;
}
使用离线访问和提示:同意对我来说效果很好:
auth2 = gapi.auth2.init({
client_id: '{cliend_id}'
});
auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback);
现在谷歌在我的请求中拒绝了这些参数(access_type,prompt)...... :(并且根本没有“撤销访问”按钮。我很沮丧因为找回了我的 refresh_token 哈哈
更新:我在这里找到了答案:D 您可以通过请求 https://developers.google.com/identity/protocols/OAuth2WebServer 取回刷新令牌
curl -H "Content-type:application/x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token={token} 令牌可以是访问令牌或刷新令牌。如果令牌是访问令牌并且有相应的刷新令牌,那么刷新令牌也会被撤销。如果撤销处理成功,则响应的状态码为 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
我的解决方案有点奇怪..我尝试了我在互联网上找到的所有解决方案,但什么也没有。令人惊讶的是,这奏效了:删除 credentials.json,刷新,再次在您的帐户中更新您的应用程序。新的 credentials.json 文件将具有刷新令牌。将此文件备份到某处。然后继续使用您的应用程序,直到刷新令牌错误再次出现。删除现在仅带有错误消息的 crendetials.json 文件(在我的情况下发生这种情况),然后将旧凭据文件粘贴到文件夹中,完成!自从我这样做以来已经 1 周了,没有更多问题了。
为了在每次身份验证时获得新的 refresh_token,在仪表板中创建的 OAuth 2.0 凭据的类型应为“其他”。同样如上所述,在生成 authURL 时应使用 access_type='offline' 选项。
当使用类型为“Web 应用程序”的凭据时,提示/approval_prompt 变量的组合将不起作用 - 您仍将仅在第一次请求时获得 refresh_token。
将 access_type=offline
添加到授权 Google 授权 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();
这里的重要部分是附加了 ?access_type=offline
的授权 URI。
refresh_token
时,您在所有情况下都需要access_type=offline
。$client->setAccessType('offline')
一起工作。默认情况下,function setApprovalPrompt()
已在force
中传递。