【问题标题】:google API service account flow in perlperl 中的 google API 服务帐户流程
【发布时间】:2013-07-07 17:45:42
【问题描述】:

我需要在 perl 中使用 Google 的服务帐户流程对应用程序进行身份验证和授权。 Google 似乎没有在其文档中将 perl 列为受支持的语言。 有没有人遇到过这个问题?指向任何代码的指针?

【问题讨论】:

    标签: oauth-2.0 google-oauth


    【解决方案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->{gapi}{client_id},
          secret        => $config->get( 'gapi/client_secret' ),                                #$config->{gapi}{client_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__
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-30
      • 2013-05-25
      • 1970-01-01
      • 2014-06-23
      • 2013-08-21
      • 1970-01-01
      • 2022-11-08
      • 1970-01-01
      相关资源
      最近更新 更多