ChatGPT解决这个技术问题 Extra ChatGPT

未收到 Google OAuth 刷新令牌

我想从 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
}
我有一个类似的问题。检查我的答案here

g
glidester

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


这对我不起作用,但添加参数“access_type=offline”似乎可以解决问题:developers.google.com/accounts/docs/OAuth2WebServer#offline
当您需要 refresh_token 时,您在所有情况下都需要 access_type=offline
但是在这种情况下,我如何在令牌到期后刷新令牌?
@vivek_jonam 存储刷新令牌和到期日期。当它过期时,您使用刷新令牌请求新令牌。见这里:developers.google.com/accounts/docs/OAuth2WebServer#refresh
我让它与 $client->setAccessType('offline') 一起工作。默认情况下,function setApprovalPrompt() 已在 force 中传递。
P
Prasad Jadhav

为了获得刷新令牌,您必须同时添加 approval_prompt=forceaccess_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");

在节点中: var authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES,approval_prompt:'force' });
令人愤慨的是,谷歌没有在他们的文档中解决这个问题,或者至少在我已经盯着 7 个小时的 php 或 oath2 文档中没有解决这个问题。为什么这在他们的文档中不是粗体字
谢谢!此处的文档 (github.com/googlesamples/apps-script-oauth2) 对该参数非常具有误导性。当我添加approval_prompt=force 时,我终于得到了一个刷新令牌。
approval_prompt=force 对我不起作用,但 prompt=consent 对我有用。
N
Norbert

我搜索了一个漫长的夜晚,这就是诀窍:

从 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);

完美@Norbert,这正是我所需要的。
谢谢!我的问题的确切答案@Norbert
你解决了我的生活
E
Eaten by a Grue

我想为那些遇到此问题的沮丧灵魂添加有关此主题的更多信息。为离线应用获取刷新令牌的关键是确保您展示的是同意屏幕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


要添加,如果您使用 GoLang oauth 库,您可以通过 oauth2.ApprovalForce AuthCodeOption: pkg.go.dev/golang.org/x/oauth2#AuthCodeOption 进行设置
j
jeteon

这让我有些困惑,所以我想我会分享一下我辛苦学习的东西:

当您使用 access_type=offlineapproval_prompt=force 参数请求访问时,您应该同时收到 access 令牌和 refresh 令牌。 access 令牌在您收到后很快就会过期,您需要刷新它。

您正确地发出了获取新访问令牌的请求,并收到了包含新访问令牌的响应。我也对我没有获得新的刷新令牌这一事实感到困惑。但是,这就是它的本意,因为您可以一遍又一遍地使用相同的刷新令牌。

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


我有一个 CMS,其中不同的用户使用不同的谷歌帐户连接到分析 api。但是,有时多个用户可以使用同一个公司 Google 帐户进行连接,但每个用户都希望访问不同的 Analytics 帐户。只有第一个接收刷新令牌,而所有其他人都没有,因此必须每小时重新连接。有没有办法为后续身份验证获取相同的刷新令牌,而不仅仅是在一小时内到期的 access_token?
API 似乎只生成一次刷新令牌。令牌的任何“共享”都必须在您的代码中进行。但是,您必须小心不要意外地向用户授予新的访问权限。一个简单的方法是让应用程序在它们自己的存储(SQLese 中的单独“表”)中跟踪刷新令牌和关联帐户。然后,当您想获得一个新的访问令牌时,您可以从那里检查并使用这个可能的通用令牌。以某种方式实现,您的代码不需要知道谁实际获得了令牌。
我不知道如何确定应该将哪个刷新令牌与刚刚获得的新访问令牌相关联。有不同的用户进行登录,唯一的共同点是他们使用相同的 Google 帐户(电子邮件)连接到 API。但是 Google 不会发回帐户的 ID 或电子邮件,它只是发回一个令牌。所以我不知道如何关联 2 个不同的 CMS 用户...
我已经在这里充分解释了我的问题:stackoverflow.com/questions/30217524/…
Youtube oAuth2 refresh_token 仅在使用强制时显示。
C
Community

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


非常次要的细节,但很重要的一个。救了我 !谢谢 :)
@ZackMorris 所以..你的意思是说我不能使用访问令牌从后端获取 refresh_token 吗?
@Nevermore 您无法从 access_token 本身获得 refresh_token 。如果您希望您的服务器处理刷新,那么您需要第一次将 refresh_token 存储在您的数据库中。此外,如果您在前端执行客户端 OAuth 流程,则用户如果希望服务器为他们刷新,则必须将其 refresh_token 发送到后端。
c
coreyward

为了获得 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 帐户进行身份验证(即,您是不使用它进行登录/身份验证)。


我认为在不存储任何个人信息的情况下识别用户的部分是关键。感谢您指出这一点,我没有在谷歌文档上看到任何关于此的参考。
M
Mazahir Haroon

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 中编写的步骤删除您的应用程序的访问权限。


j
jarraga

我正在使用 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)
        })
    })
}

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

Online parameters extractor

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

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

我希望这些信息有用


这似乎是一个正确的方法。在用户从 Google 帐户设置撤消访问权限并且如果您调用“oAuth2Client.revokeToken”(如果您的应用支持“取消链接”,您应该调用它)后,这非常有效
a
apadana

设置此项将导致每次发送刷新令牌:

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

j
jeteon

对我来说,我正在试用 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;
}

C
Carlos Eduardo Ki Lee

使用离线访问和提示:同意对我来说效果很好:

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 

T
Tran Tien Duc

现在谷歌在我的请求中拒绝了这些参数(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 和错误码。


Y
Yordan Georgiev
    #!/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

G
Guilherme Canoa

我的解决方案有点奇怪..我尝试了我在互联网上找到的所有解决方案,但什么也没有。令人惊讶的是,这奏效了:删除 credentials.json,刷新,再次在您的帐户中更新您的应用程序。新的 credentials.json 文件将具有刷新令牌。将此文件备份到某处。然后继续使用您的应用程序,直到刷新令牌错误再次出现。删除现在仅带有错误消息的 crendetials.json 文件(在我的情况下发生这种情况),然后将旧凭据文件粘贴到文件夹中,完成!自从我这样做以来已经 1 周了,没有更多问题了。


M
Martin Kask

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

当使用类型为“Web 应用程序”的凭据时,提示/approval_prompt 变量的组合将不起作用 - 您仍将仅在第一次请求时获得 refresh_token。


g
gil.fernandes

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。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅