未收到Google OAuth刷新令牌


270

我想从Google获取访问令牌。 Google 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
}

我有一个类似的问题。在这里
Arithran '16

Answers:


675

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的Web服务器应用程序OAuth 2.0页面)。

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


20
这对我不起作用,但是添加参数“ access_type = offline”似乎可以解决问题:developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse

87
你需要access_type=offline当你想在所有情况下refresh_token
DanH

5
但是在这种情况下,如何在令牌到期后刷新呢?
vivek_jonam

5
@vivek_jonam存储刷新令牌和有效期。过期时,您需要使用刷新令牌来请求新令牌。请参阅此处:developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis 2013年

4
我明白了$client->setAccessType('offline')。在function setApprovalPrompt()已经过去了的force,默认情况下。
moey 2015年

57

为了得到刷新令牌,你必须同时添加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");

在节点中:var authUrl = oauth2Client.generateAuthUrl({access_type:'offline',scope:SCOPES,rovaling_prompt:'force'});
乔里斯·芒斯

2
谷歌没有在他们的文档中解决这个问题,或者至少在我一直盯着看了7个小时的php或oath2文档中都没有解决这个问题。为什么在世界上是他们本文档中没有大粗体文字
科林Rickels

谢谢!此处的文档(github.com/googlesamples/apps-script-oauth2)关于此参数非常有误导性。当我添加了rovaling_prompt = force时,我终于得到了刷新令牌。
Alex Zhevzhik

28

我搜索了一个漫长的夜晚,这是在解决问题:

从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
varun teja-MVT

16

这使我有些困惑,所以我想我将分享我所学到的艰难方法:

使用access_type=offlineapproval_prompt=force参数请求访问时,您应该同时收到访问令牌和刷新令牌。在访问你收到它,你将需要刷新之后令牌即将到期。

您正确提出了获取新访问令牌的请求,并收到了包含您新访问令牌的响应。我没有得到新的刷新令牌,这也使我感到困惑。但是,这就是它的原意,因为您可以反复使用相同的刷新令牌。

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


1
我有一个CMS,不同的用户在其中使用不同的Google帐户来连接到Analytics API。但是,有时有几个用户可以使用相同的公司google帐户进行连接,但每个用户都希望访问不同的Google Analytics(分析)帐户。只有第一个接收刷新令牌,而其他所有令牌都不接收刷新令牌,因此必须每小时重新连接一次。是否有办法获取SAME刷新令牌以进行后续身份验证,而不是仅在一个小时内到期的access_token?
SsjCosty 2015年

1
该API似乎只产生一次刷新令牌。令牌的任何“共享”都必须在您的代码中发生。但是,您必须小心不要意外地给用户新的访问权限。一种简单的方法是让应用程序跟踪刷新 令牌和关联帐户在其自己的存储中(SQLese中的单独“表”)。然后,当您想要获取新的访问令牌时,请检查并从那里使用该可能的通用令牌。通过某种方式实现,您的代码不需要知道实际获得令牌的人。
jeteon 2015年

1
我不知道如何确定应该将哪个刷新令牌与刚刚获得的新访问令牌相关联。登录的用户不同,唯一的共同点是他们使用相同的Google帐户(电子邮件)连接到API。但是Google不会发送回该帐户或电子邮件的ID,而只是发送回一个令牌。因此,我不知道如何关联2个不同的CMS用户...
SsjCosty 2015年

:我已经在这里充分说明我的问题stackoverflow.com/questions/30217524/...
SsjCosty

Youtube oAuth2 refresh_token仅在强制使用时显示。
德米特里·普洛什金

7

Rich Sutton的答案终于对我有用,因为我意识到添加access_type=offline是在前端客户端的授权码请求上完成的,而不是在将该代码交换为access_token的后端请求上完成的。我已经在他的答案和此Google链接上添加了评论,以获取有关刷新令牌的更多信息。

PS:如果您使用的是Satellizer,请按以下步骤将此选项添加到AngularJS的$ authProvider.google中


非常小的细节,但很重要。救了我 !谢谢 :)
德克斯特

@ZackMorris所以..您是说我无法使用访问令牌从后端获取refresh_token吗?
再也没有

@Nevermore您无法从access_token本身获取refresh_token。如果希望服务器处理刷新,则需要在第一次时将refresh_token存储在数据库中。同样,如果您正在前端执行客户端OAuth流,那么如果用户希望服务器为其刷新,则用户将不得不将其refresh_token发送到后端。
扎克·莫里斯

4

为了得到refresh_token您需要access_type=offline在OAuth请求URL中添加。当用户首次通过身份验证时,您将获得一个非零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(分析)的只读访问,另一个是just profile,这是OpenID Connect标准。

这将导致Google提供一个id_tokenget_token响应中调用的附加属性。要从id_token中获取信息,在Google文档中查看此页面。Google提供了一些库来为您验证和“解码”该库(我使用了Ruby google-id-token gem)。解析后,该sub参数实际上就是唯一的Google帐户ID。

值得注意的是,如果您更改范围,则将为已经使用原始范围进行身份验证的用户再次获取刷新令牌。例如,如果您已经有很多用户并且不想让他们都对Google中的应用进行身份验证,这将非常有用。

哦,还有最后一点:您不需要 prompt=select_account,但是如果您的用户可能想使用多个Google帐户进行身份验证(即您没有使用该帐户进行登录/身份验证),则该功能非常有用。 。


我认为识别用户而不存储任何个人信息的部分是关键。感谢您指出这一点,我在Google文档上没有看到任何有关此内容的参考。
Danielo515

3

1。如何获得“ refresh_token”?

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

2.但是即使使用“ access_type = offline”,我也无法获得“ refresh_token”吗?

解:请注意,您只会在第一个请求上获得它,因此,如果将其存储在某个地方,并且有一项规定可以在以前的到期后获取新的access_token时在代码中覆盖它,那么请确保不要覆盖此值。

来自Google Auth Doc :(此值= access_type)

该值指示Google授权服务器在您的应用程序第一次将授权代码交换为令牌时返回刷新令牌和访问令牌。

如果您再次需要'refresh_token',则需要按照Rich Rich Sutton的答案中的步骤删除对应用程序的访问权限。


2

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

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

1

对我来说,我正在尝试CalendarSampleServletGoogle提供的服务。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;
}

0

现在,谷歌已经拒绝了我的请求中的那些参数(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}

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

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


0
    #!/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

0

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

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

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

0

我的解决方案有点怪异。我尝试了在互联网上找到的所有解决方案,但没有任何尝试。令人惊讶的是,它的工作原理是:删除credentials.json,刷新,再次在您的帐户中设置您的应用程序。新的凭据.json文件将具有刷新令牌。将此文件备份到某处。然后继续使用您的应用程序,直到再次出现刷新令牌错误。删除现在仅带有错误消息的crendetials.json文件(在我的情况下已解决),然后将旧的凭据文件粘贴到文件夹中,完成了!自从ive完成这个任务已经1周了,再也没有问题了。


0

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

当使用类型为“ Web应用程序”的凭据时,不能使用hint / approval_prompt变量的组合-您仍将仅在第一个请求时获得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.