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

当前回答

将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。

其他回答

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

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

解决方案是在oAuth2Client的settings对象中添加值为consent的promp属性。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

我希望这些信息对你有用

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

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

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

prompt=consent

or

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

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)

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

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