perl 中的 google API 服务帐户流程
Posted
技术标签:
【中文标题】perl 中的 google API 服务帐户流程【英文标题】:google API service account flow in perl 【发布时间】:2013-07-07 17:45:42 【问题描述】:我需要在 perl 中使用 Google 的服务帐户流程对应用程序进行身份验证和授权。 Google 似乎没有在其文档中将 perl 列为受支持的语言。 有没有人遇到过这个问题?指向任何代码的指针?
【问题讨论】:
【参考方案1】:搜索 perl Google OAUTH,您会发现许多不同的方法。
有关可用于为您适当配置的 Google Cloud API 项目收集 OAUTH 令牌的快速网络服务器示例,请参阅以下内容:
#!perl
use strict; use warnings; ## required because I can't work out how to get percritic to use my modern config
package goauth;
# ABSTRACT: CLI tool with mini http server for negotiating Google OAuth2 Authorisation access tokens that allow offline access to Google API Services on behalf of the user.
#
# Supports multiple users
# similar to that installed as part of the WebService::Google module
# probably originally based on https://gist.github.com/throughnothing/3726907
# OAuth2 for Google. You can find the key (CLIENT ID) and secret (CLIENT SECRET) from the app console here under "APIs & Auth"
# and "Credentials" in the menu at https://console.developers.google.com/project.
# See also https://developers.google.com/+/quickstart/.
use strict;
use warnings;
use Carp;
use Mojolicious::Lite;
use Data::Dumper;
use Config::JSON;
use Tie::File;
use feature 'say';
use Net::EmptyPort qw(empty_port);
use Crypt::JWT qw(decode_jwt);
my $filename;
if ( $ARGV[0] )
$filename = $ARGV[0];
else
$filename = './gapi.json';
if ( -e $filename )
say "File $filename exists";
input_if_not_exists( ['gapi/client_id', 'gapi/client_secret', 'gapi/scopes'] ); ## this potentially allows mreging with a json file with data external
## to the app or to augment missing scope from file generated from
## earlier versions of goauth from other libs
runserver();
else
say "JSON file '$filename' with OAUTH App Secrets and user tokens not found. Creating new file...";
setup();
runserver();
sub setup
## TODO: consider allowing the gapi.json to be either seeded or to extend the credentials.json provided by Google
my $oauth = ;
say "Obtain project app client_id and client_secret from http://console.developers.google.com/";
print "client_id: ";
$oauth-> client_id = _stdin() || croak( 'client_id is required and has no default' );
print "client_secret: ";
$oauth-> client_secret = _stdin() || croak( 'client secret is required and has no default' );
print 'scopes ( space sep list): eg - email profile https://www.googleapis.com/auth/plus.profile.emails.read '
. "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/contacts.readonly https://mail.google.com\n";
$oauth-> scopes = _stdin(); ## no croak because empty string is allowed an will evoke defaults
## set default scope if empty string provided
if ( $oauth-> scopes eq '' )
$oauth-> scopes
= 'email profile https://www.googleapis.com/auth/plus.profile.emails.read '
. 'https://www.googleapis.com/auth/calendar '
. 'https://www.googleapis.com/auth/contacts.readonly https://mail.google.com';
my $tokensfile = Config::JSON->create( $filename );
$tokensfile->set( 'gapi/client_id', $oauth-> client_id );
$tokensfile->set( 'gapi/client_secret', $oauth-> client_secret );
$tokensfile->set( 'gapi/scopes', $oauth-> scopes );
say 'OAuth details updated!';
# Remove comment for Mojolicious::Plugin::JSONConfig compatibility
tie my @array, 'Tie::File', $filename or croak $!;
shift @array;
untie @array;
return 1;
sub input_if_not_exists
my $fields = shift;
my $config = Config::JSON->new( $filename );
for my $i ( @$fields )
if ( !defined $config->get( $i ) )
print "$i: ";
#chomp( my $val = <STDIN> );
my $val = _stdin();
$config->set( $i, $val );
return 1;
sub runserver
my $port = empty_port( 3000 );
say "Starting web server. Before authorization don't forget to allow redirect_uri to http://127.0.0.1 in your Google Console Project";
$ENV 'GOAUTH_TOKENSFILE' = $filename;
my $config = Config::JSON->new( $ENV 'GOAUTH_TOKENSFILE' );
# authorize_url and token_url can be retrieved from OAuth discovery document
# https://github.com/marcusramberg/Mojolicious-Plugin-OAuth2/issues/52
plugin "OAuth2" =>
google =>
key => $config->get( 'gapi/client_id' ), # $config->gapiclient_id,
secret => $config->get( 'gapi/client_secret' ), #$config->gapiclient_secret,
authorize_url => 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code',
token_url => 'https://www.googleapis.com/oauth2/v4/token' ## NB Google credentials.json specifies "https://www.googleapis.com/oauth2/v3/token"
;
# Marked for decomission
# helper get_email => sub
# my ( $c, $access_token ) = @_;
# my %h = ( 'Authorization' => 'Bearer ' . $access_token );
# $c->ua->get( 'https://www.googleapis.com/auth/plus.profile.emails.read' => form => \%h )->res->json;
# ;
helper get_new_tokens => sub
my ( $c, $auth_code ) = @_;
my $hash = ;
$hash-> code = $c->param( 'code' );
$hash-> redirect_uri = $c->url_for->to_abs->to_string;
$hash-> client_id = $config->get( 'gapi/client_id' );
$hash-> client_secret = $config->get( 'gapi/client_secret' );
$hash-> grant_type = 'authorization_code';
my $tokens = $c->ua->post( 'https://www.googleapis.com/oauth2/v4/token' => form => $hash )->res->json;
return $tokens;
;
get "/" => sub
my $c = shift;
$c-> config = $config;
app->log->info( "Will store tokens in" . $config->getFilename( $config->pathToFile ) );
if ( $c->param( 'code' ) ) ## postback from google
app->log->info( "Authorization code was retrieved: " . $c->param( 'code' ) );
my $tokens = $c->get_new_tokens( $c->param( 'code' ) );
app->log->info( "App got new tokens: " . Dumper $tokens);
if ( $tokens )
my $user_data;
if ( $tokens-> id_token )
# my $jwt = Mojo::JWT->new(claims => $tokens->id_token);
# carp "Mojo header:".Dumper $jwt->header;
# my $keys = $c->get_all_google_jwk_keys(); # arrayref
# my ($header, $data) = decode_jwt( token => $tokens->id_token, decode_header => 1, key => '' ); # exctract kid
# carp "Decode header :".Dumper $header;
$user_data = decode_jwt( token => $tokens-> id_token , kid_keys => $c->ua->get( 'https://www.googleapis.com/oauth2/v3/certs' )->res->json, );
#carp "Decoded user data:" . Dumper $user_data;
#$user_data->email;
#$user_data->family_name
#$user_data->given_name
# $tokensfile->set('tokens/'.$user_data->email, $tokens->access_token);
$config->addToHash( 'gapi/tokens/' . $user_data-> email , 'access_token', $tokens-> access_token );
if ( $tokens-> refresh_token )
$config->addToHash( 'gapi/tokens/' . $user_data-> email , 'refresh_token', $tokens-> refresh_token );
else ## with access_type=offline set we should receive a refresh token unless user already has an active one.
carp('Google JWT Did not incude a refresh token - when the access token expires services will become inaccessible');
$c->render( json => $config->get( 'gapi' ) );
else ## PRESENT USER DEFAULT PAGE TO REQUEST GOOGLE AUTH'D ACCESS TO SERVICES
$c->render( template => 'oauth' );
;
app->secrets( ['putyourownsecretcookieseedhereforsecurity' . time] ); ## NB persistence cookies not required beyond server run
app->start( 'daemon', '-l', "http://*:$port" );
return 1;
## replacement for STDIN as per https://coderwall.com/p/l9-uvq/reading-from-stdin-the-good-way
sub _stdin
my $io;
my $string = q;
$io = IO::Handle->new();
if ( $io->fdopen( fileno( STDIN ), 'r' ) )
$string = $io->getline();
$io->close();
chomp $string;
return $string;
=head2 TODO: Improve user interface of the html templates beneath DATA section
=over 1
=item * include Auth with Google button from Google Assets and advertise scopes reqeusted on the oauth.html
=item * More informative details on post-authentication page - perhaps include scopes, filename updated and instructions on revoking
=back
=cut
__DATA__
@@ oauth.html.ep
<%= link_to "Click here to get Google OAUTH2 tokens", $c->oauth2->auth_url("google",
authorize_query => access_type => 'offline',
scope => $c->config->get('gapi/scopes'), ## scope => "email profile https://www.googleapis.com/auth/plus.profile.emails.read https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/contacts.readonly",
)
%>
<br>
<br>
<a href="https://developers.google.com/+/web/api/rest/oauth#authorization-scopes">
Check more about authorization scopes</a>
Once you have a token in your gapi.json you can check the available scopes with curl using <pre>curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=<YOUR_ACCESS_TOKEN></pre>
__END__
【讨论】:
以上是关于perl 中的 google API 服务帐户流程的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Perl 中的 Email::MIME 从 google 群组帐户发送电子邮件/抄送不接收电子邮件
Google Calendar API - 禁止 - 服务帐户错误
Google API 授权(服务帐户)错误:HttpAccessTokenRefreshError:未授权客户端:请求中的未授权客户端或范围