【问题标题】:Not receiving Google OAuth refresh token未收到 Google OAuth 刷新令牌
【发布时间】:2012-06-05 09:00:00
【问题描述】:

我想从 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

标签: gdata gdata-api access-token


【解决方案1】:

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 重定向(请参阅 Google 的 OAuth 2.0 for Web Server Applications 页面)。

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

【讨论】:

【解决方案2】:

为了获得刷新令牌,您必须同时添加 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 对我有用。
【解决方案3】:

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

从 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
【解决方案4】:

这让我有些困惑,所以我想我会分享一下我在艰难中学到的东西:

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

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

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

【讨论】:

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

我想为遇到此问题的那些沮丧的灵魂添加有关此主题的更多信息。为离线应用获取刷新令牌的关键是确保您展示的是同意屏幕refresh_token 仅在用户通过单击“允许”授予授权后立即返回。

在我在开发环境中进行了一些测试并因此已经在给定帐户上授权了我的应用程序之后,我(我怀疑还有许多其他问题)出现了这个问题。然后我转到生产环境并尝试使用已授权的帐户再次进行身份验证。在这种情况下,同意屏幕不会再次出现,并且 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

补充阅读:

【讨论】:

【解决方案6】:

Rich Sutton's answer 终于为我工作了,在我意识到添加access_type=offline 是在前端客户端请求授权码时完成的,不是后端请求将该代码交换为 access_token。我在他的回答中添加了评论,并在this link at Google 中添加了有关刷新令牌的更多信息。

附:如果您使用的是 Satellizer,请here is how to add that option to the $authProvider.google in AngularJS

【讨论】:

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

为了获得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 帐户进行身份验证(即,您没有将其用于登录/身份验证)。

【讨论】:

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

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

【讨论】:

    【解决方案9】:

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

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

    【讨论】:

      【解决方案10】:

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

      【讨论】:

        【解决方案11】:

        使用 离线访问prompt:consent 对我来说效果很好:

           auth2 = gapi.auth2.init({
                            client_id: '{cliend_id}' 
           });
        
           auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
        

        【讨论】:

          【解决方案12】:

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

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

          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

          希望这些信息有用

          【讨论】:

            【解决方案13】:

            现在谷歌在我的请求中拒绝了这些参数(access_type,提示)...:(而且根本没有“撤销访问”按钮。我很沮丧,因为拿回了我的 refresh_token 哈哈

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

            curl -H "内容类型:应用程序/x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token={token}

            令牌可以是访问令牌或刷新令牌。如果token是访问token并且有对应的刷新token,那么刷新token也会被撤销。

            如果撤销处理成功,则响应的状态码为 200。对于错误情况,返回状态码 400 和错误码。

            【讨论】:

              【解决方案14】:
                  #!/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
              

              【讨论】:

                【解决方案15】:

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

                【讨论】:

                  【解决方案16】:

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

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

                  【讨论】:

                    【解决方案17】:

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

                    这里的重要部分是授权 URI,其中附加了?access_type=offline

                    【讨论】:

                      猜你喜欢
                      • 2017-01-11
                      • 2015-06-27
                      • 1970-01-01
                      • 2021-11-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-11-21
                      • 2017-06-09
                      • 1970-01-01
                      相关资源
                      最近更新 更多