PythonプログラムでGoogle認証してGoogleのサービスを利用する

プログラムの中でGoogleのサービス(API)を操作するとき、Google認証が必要になる。しかしこれがややこしく、Googleの公式のドキュメントの記述も古かったりサービスの種類によって方法がばらばらだったりして分かりにくい。この記事ではこれを整理して説明する。

プログラムでGoogle認証する場面

具体的なケースを想定するとわかりやすいのだが、

たとえばGoogleアナリティクスからAPIでデータ取得し、そのデータをGoogle Cloud StorageやBigQueryに書き込みする場合、Googleアナリティクスのレポート閲覧権限(特定のビューに紐づいた)とGCPの権限(Google Cloud Storageの書き込み権限など)が必要になる。その権限を持ったGoogleアカウントで認証をすることになる。

この認証方法には大きく2通りの方法がある。

2通りの認証方法

  1. サービスアカウント認証:必要な権限を付与されたアカウント(プログラム用の特殊なアカウント=サービスアカウント)を準備し、そのアカウントを使ってプログラムを実行する
  2. ユーザアカウント認証:一般のユーザ権限でプログラムを実行する

プログラムの挙動としてはどちらを選んでもいいが、使い道に依存する。

1の場合、たとえばGoogleアナリティクスでは特定のビューに対する権限をあらかじめサービスアカウントに付与しておいて、そのビューに限定したデータにアクセスする用途になる。

2の場合はプログラムを使う人(ユーザ)が実行時に自分の権限をプログラムに与えるイメージ。アクセスできるビューはユーザ次第である(使うユーザによって異なる)。

サービスアカウントとは

プログラム用の特殊なアカウント。Googleの画面にはログインできない。人間でない者(プログラムなど)が使うために設計されたアカウントである。

こんな形式のID

my-user-name@my-project.iam.gserviceaccount.com

GCPまたはGoogle Developerの管理画面で発行し、その後権限を付与する。

ユーザアカウントとは

ユーザがパスワードを入力してログインするアカウント。GmailやG Suiteなどで使っているGoogleアカウントで、使うサービスの権限(特定のビューに対するGoogleアナリティクスの閲覧権限など)が付与されたもの。

サービスアカウントとユーザアカウントの違い

バッチプログラムのようにプログラムがスタンドアロンで動くアプリはサービスアカウント、人間が使うアプリはユーザアカウントを想定することが多い。もちろんプログラムがスタンドアロンで使うケースであってもユーザアカウントを使うことはある。

利用するサービス側(Googleアナリティクスなど)で管理者が追加的に権限を付与できないケースもポリシーによってはあり、その場合はユーザ権限での認証を採用する。

たとえばGoogleアナリティクスの場合、アナリティクスの管理者が誰に対して権限を付与するかの違いになる。

サービスアカウントを使う場合、アナリティクスの管理者がサービスアカウントに権限を付与する。権限が不要になった時には管理者がサービスアカウントに付与した権限を削除することになる。管理者がプログラムによるアクセスを承認する手続きである。

それに対してユーザアカウント認証では、アナリティクスの管理者がプログラムの実行者に直接権限を付与することはない。管理者に権限を与えられたユーザがさらにプログラムに対して権限を付与する(サービスの利用を承認する)ことになる。管理者がプログラムのアクセスを承認するのではなく、ユーザが勝手に(自分の権限の範囲内で)プログラムを利用しているという位置づけである。

これはアナリティクス以外のGoogleサービスでも同様である。

サービスアカウントは管理者が権限設定で権限をはく奪しない限りそのサービスを使えなくなることはないが、ユーザアカウントによって認証されたものでは時間が経過すると使えなくなることがある。その場合は再びユーザが権限を付与する手続きをしなければ使えない。したがって永続的に動作するプログラムの場合にはサービスアカウントを使うのが望ましい。

重要なのはどちらでもGoogleのサービスを利用することは可能だということ。それらの性質を分かったうえで状況別に使いこなせるようになることである。

プログラムがGoogleサービスを利用するまでの全体の流れ

  1. 準備
  2. プログラムの実行
    1. 認証→scoped_credentialsの取得
    2. scoped_credentialsを使って個別サービスのAPIを呼び出す

準備と認証の方法がサービスアカウントとユーザアカウントによって異なる。その後の個別サービスを呼び出すところは共通。

準備から認証まで(サービスアカウントとユーザアカウントで違う部分)

サービスアカウントの場合

以下の例はGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに格納するケースを想定する。つまりGoogleアナリティクス(読込のみ)とBigQueryを使う。

サービスアカウントの発行と認証キーの取得

GCP/Google Developerのサービスアカウントの管理画面でサービスアカウントを発行し、認証キーを取得する。

サービスアカウントの発行画面は

GCPの管理画面「IAMとサービス」→「サービス アカウント」
Google Developer Consoleの画面「IAMと管理」→「サービス アカウント」

またはGCP/Google Developer Console共通で

「APIとサービス」→左メニューの「認証情報」→「認証情報作成」→「サービス アカウント」

(どちらでもいい)

認証キーはJSONで取得する。このJSONに認証で必要な情報がすべて含まれている(パスワードが別などということはない)。

サービスアカウントにGoogleサービスの権限を付与する

権限付与はサービスによって大きく異なる。

  • GCP(BigQueryやストレージなど)→IAMの設定画面
  • その他Googleサービス(アナリティクス、ドライブなど)→各サービスのユーザ管理画面

→各サービスのメールアドレスとして上記のIDを指定する

Google Search Consoleの場合

Googleアナリティクスの場合

ライブラリのインストール

google.oauth2パッケージを使う。google.oauth2google-authパッケージに含まれている。

pip install -U google-auth

認証部分の実装

キーをプログラムに実装する方法は

  • パスを指定して外部のサービスアカウントファイルを読み込む
  • キーの内容(JSONテキスト)をプログラム内に記述

がある。

サービスアカウントファイルを読み込む場合

from google.oauth2 import service_account
credentials = service_account.Credentials.from_service_account_file('service-account.json')
scoped_credentials = credentials.with_scopes(
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ])

service_account.Credentials.from_service_account_file()の引数で鍵ファイルのパスを指定する。credentials.with_scopes()では今回実際に使うGoogleサービスの範囲を指定する。今回はGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに入れるのでGoogleアナリティクスの読み込み権限とGCPの利用になる。

認証済みの資格情報scoped_credentialsが生成される。これを各サービスのAPIインスタンスを生成するときに指定する。

認証キーのJSONテキストを直接プログラムファイル内に記述する場合

from google.oauth2 import service_account
service_account_key = {
  "type": "service_account",
  "project_id": "my-project",
  "private_key_id": "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIE...1X9dS\n-----END PRIVATE KEY-----\n",
  "client_email": "user1@my-project.iam.gserviceaccount.com",
  "client_id": "999999999999999999999",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/user1%40my-project.iam.gserviceaccount.com"
}
credentials = service_account.Credentials.from_service_account_info(service_account_key)
scoped_credentials = credentials.with_scopes(
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ])

service_account_keyの内容が認証キーファイルのJSONをそのまま貼り付けたものになる。コピー&ペーストでいい(そうするとPythonではdictとして読み込まれる)。

service_account.Credentials.from_service_account_info()の引数として上記のdictを指定する。あとは同じ(ここで生成されるcredentialsと先のファイル指定で生成するcredentialsは同じ)。

実行

実行時は何も入力することはない

ユーザアカウントの場合(OAuth 2.0)

サービスアカウント編と同様にGoogleアナリティクスのレポートAPIからデータを取得し、BigQueryに格納するケースを想定する。つまりGoogleアナリティクス(読込のみ)とBigQueryを使う。

ユーザアカウントを使った認証はOAuth 2.0という方式をとる。手順は以下のようになる(方法1)

  1. アプリケーション自体の認証キー(クライアントIDとクライアントシークレット)を発行する
  2. 初回実行時にユーザが手動でアプリの承認を行う
  3. 承認済み認証ファイルをローカルに保存
  4. 次回以降、認証ファイルがあればそれを使う
  5. なければ改めて手動のアプリ承認フロー

サービスアカウントの時と同様に認証済みファイル(上記の3)をプログラム内に記載する方法もある。(方法2)

ユーザアカウント認証をできるようにするための準備

以下の手順になる。GCPまたはGoogle Developer Consoleの画面から「APIとサービス」でアクセスする。

  1. APIを有効化する
    「APIとサービス」→左メニューの「ライブラリ」→使うサービスを選択して「有効化」
  2. OAuthの同意画面を設定する
    「APIとサービス」→左メニューの「OAuth 同意画面」
  3. アプリケーションの認証キー(クライアントIDとクライアントシークレット)を発行する
    「APIとサービス」→左メニューの「認証情報」→「認証情報作成」→「OAuth 2.0 クライアント ID」

(1と2は同じプロジェクトですでに一度完了している場合は改めて不要)

クライアントIDとクライアントシークレットの2種類の文字列を取得する

ユーザによる認証フローを挟む場合

こちらが一般的

ライブラリのインストール

pydata_google_authパッケージを使う。

pip install -U pydata-google-auth

認証部分の実装

import pydata_google_auth, os
CLIENT_ID = '....apps.googleusercontent.com'
CLIENT_SECRET = '...'
scoped_credentials = pydata_google_auth.get_user_credentials(
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ],
  client_id=CLIENT_ID,
  client_secret=CLIENT_SECRET,
  credentials_cache=pydata_google_auth.cache.ReadWriteCredentialsCache(dirname=os.path.abspath('.'), filename='pydata_google_credentials.json')
)

Googleの管理画面から取得したクライアントIDとクライアントシークレットをpydata_google_auth.get_user_credentials()の引数として指定する。

この認証方式ではプログラム実行時に承認を求められ、許諾すると資格情報がファイルとして保存される。そのパスとファイル名をdirname=filename=の部分で指定する。上記の例ではカレントディレクトリのpydata_google_credentials.jsonというファイル名で保存される。

実行時にユーザがアプリの承認を行う

プログラムを実行すると、ユーザがアプリケーションを承認するためのAuth Codeを発行するためのURLが生成される

そこにブラウザでアクセスし、APIにアクセスするGoogleアカウントでログインして承認する。

Auth Codeが表示されるのでコピーし、プログラムの画面に貼り付ける。

認証が完了するのでプログラムが実行完了するまで待つ。

一度取得した資格情報ファイルを直接プログラムに記載する場合

保存された資格情報ファイル(上の例ではカレントディレクトリのpydata_google_credentials.json)の内容をプログラム中にハードコーディングすることも可能。この方法は以下のとおり。

ライブラリのインストール

サービスアカウントのときと同じgoogle.oauth2パッケージを使う。google.oauth2google-authパッケージに含まれている。

pip install -U google-auth

認証部分の実装

from google.oauth2 import credentials
authorized_user_info = {"refresh_token": "1//aaaaa-bbbbbbb", "id_token": null, "token_uri": "https://accounts.google.com/o/oauth2/token", "client_id": "99999999999-999999aaaaaaaaaa9999aaaaaaaaaaaa.apps.googleusercontent.com", "client_secret": "aaaaaaaaaaaaaaaaaaaaaaaa", "scopes": ["https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/analytics.readonly"]}
scoped_credentials = credentials.Credentials.from_authorized_user_info(
  authorized_user_info,
  [
    'https://www.googleapis.com/auth/cloud-platform',
    'https://www.googleapis.com/auth/analytics.readonly'
  ]
)

authorized_user_infoの内容が資格情報ファイルのJSONをそのまま貼り付けたものになる。コピー&ペーストでいい(そうするとPythonではdictとして読み込まれる)。

credentials.Credentials.from_authorized_user_info()の引数として上記のdictを指定する。

実行

実行時は何も入力することはない

個別のサービスを利用する(サービスアカウントとユーザアカウントで共通)

サービスによって大きく2パターンある。

Google API Clientを使うタイプ

主にGCP以外のGoogleのサービス(ドライブ、アナリティクスなど)のAPIを使う場合。例外的にGCPではCompute EngineやDataflowがこれに該当する。

https://github.com/googleapis/google-api-python-client

ライブラリのインストール

パッケージgoogleapiclientをインストールする

pip install -U google-api-python-client

APIを呼び出す部分の実装

Googleアナリティクスの場合

from googleapiclient.discovery import build

# サービスインスタンスの生成
analytics = build('analyticsreporting', 'v4', credentials=scoped_credentials)
# APIにリクエストを送る
response = analytics.reports().batchGet(body=request_body).execute()

サーチコンソールの場合

from googleapiclient.discovery import build

# サービスインスタンスの生成
webmasters = build('webmasters', 'v3', credentials=scoped_credentials)
# APIにリクエストを送る
response = webmasters.searchanalytics().query(siteUrl='...', body=request_body).execute()

build()メソッドの引数credentialsで資格情報オブジェクトscoped_credentialsを指定する。

from googleapiclient.discovery import build
build('サービス名', 'vバージョン', credentials=scoped_credentials)

Google Cloud Python Clientを使うタイプ

BigQueryやCloud Storageなど多くのGCPのサービスが該当する。

https://github.com/googleapis/google-cloud-python

ライブラリのインストール

GCPのサービスごとのパッケージgoogle-cloud-*****をインストールする

pip install -U google-cloud-bigquery
pip install -U google-cloud-storage

APIを呼び出す部分の実装

Cloud Storageの場合

from google.cloud import storage

# サービスインスタンスの生成
gcs_client = storage.Client(credentials=scoped_credentials, project='my-project')
# サービスを使う
bucket = gcs_client.get_bucket(CLOUD_STORAGE_BUCKET)
blob = bucket.blob('aaa.txt')
blob.upload_from_string('...', content_type='text/plain')

BigQueryの場合

from google.cloud import bigquery

# サービスインスタンスの生成
bigquery_client = bigquery.Client(credentials=scoped_credentials, project='my-project')
# サービスを使う
query_job = bigquery_client.query('INSERT ...')
query_job.result()

各サービスのClient()メソッドの引数credentialsで資格情報オブジェクトscoped_credentialsを指定する。

なおPandasでBigQueryにデータを入れる場合は

import pandas as pd
df = pd.DataFrame(rows)
df.to_gbq('mydataset.ga_result', project_id='my-project', credentials=scoped_credentials, chunksize=100000, if_exists='append')

df.to_gbq()メソッドの引数credentialsで資格情報オブジェクトscoped_credentialsを指定する。

以上を実装したサンプルは

https://github.com/twofacauth/google-analytics-utils/blob/master/py/ga_to_bigquery.py

にある。

応用編:複数のscoped_credentials(資格情報)を使う

上記は一つのアカウントに使用するすべてのGoogleサービスの権限を付与した。しかし実際にはGoogleサービスによって適用したい権限のアカウントが違う場合がある。たとえば

  • BigQueryはベンダ側で管理
  • Googleアナリティクスは事業会社側で管理

という状況で、お互いに相手の会社のアカウントに対して自社の権限を付与したくないというケースがある。

そうなるとBigQueryはベンダ側のサービスアカウントで、アナリティクスは事業会社のユーザ権限となる。それぞれのscoped_credentialsを使い分ければいい。

  • scoped_credentials1 -> ベンダのサービスアカウントから発行した資格情報
  • scoped_credentials2 -> 事業会社側のユーザアカウントから発行した資格情報
from google.oauth2 import service_account
import pydata_google_auth, os
from googleapiclient.discovery import build
from google.cloud import bigquery

# ベンダ側の認証(BigQuery)
credentials = service_account.Credentials.from_service_account_file('vendor-service-account.json')
scoped_credentials1 = credentials.with_scopes(
  [
    'https://www.googleapis.com/auth/cloud-platform',
  ])

# ユーザ側の認証(アナリティクス)
CLIENT_ID = '.....apps.googleusercontent.com'
CLIENT_SECRET = '...'
scoped_credentials2 = pydata_google_auth.get_user_credentials(
  [
    'https://www.googleapis.com/auth/analytics.readonly'
  ],
  client_id=CLIENT_ID,
  client_secret=CLIENT_SECRET,
  credentials_cache=pydata_google_auth.cache.ReadWriteCredentialsCache(dirname=os.path.abspath('.'), filename='pydata_google_credentials.json')
)

# アナリティクスのAPIにアクセスする
analytics = build('analyticsreporting', 'v4', credentials=scoped_credentials1)
response = analytics.reports().batchGet(body=request_body).execute()
  :

# BigQueryを呼び出す
bigquery_client = bigquery.Client(credentials=scoped_credentials2, project='my-project')
query_job = bigquery_client.insert_rows('mydataset.ga', ...)

アナリティクスとBigQueryの引数credentialsで指定する資格情報を使い分けている。

データ周辺の技術 の記事一覧