Google系のサービスの認証は大体JSON!
2023/9/22追記、9/28修正あり
※Pythonでの紹介となります
プログラムからGoogleSheetの操作をするSheetsAPIやNoSQLでデータが保持できるFirebaseなどGoogle系のサービスに接続する場合、秘密鍵が入ったJSONファイルをダウンロードして、その情報から接続を行います。
これは慣れると便利なのですがCloudFunctionsからGoogleSheetを読み取るものを作ろうとしたとき気づきました…
ファイルを置ける場所がない…
CloudFunctionsなどサーバレスのサービスはどんな小さなファイルもサーバに置いておくことができません。またサーバがある場合でもこのようなファイルを置いておくのは、セキュリティ上なんか不安…なんて方もいるかと思います。
失礼しました。新規でファイルを作成すればmain.pyと同じディレクトリのファイルとして扱うことができます。ただファイルを置きたくない方のために以下の方法を残しておきます。
JSONファイルを使わずに認証をする方法
そこで…
環境変数から取得して、その場でJSONファイル作ればいいじゃん!
ってのを思いつき、プログラマ心をくすぐられたのですが、そんなことする必要はありませんでした。
SheetsAPI:from_json_keyfile_dictを利用
結果から言うとJSONの中身の一部をdict型の変数に入れて、それをセットすればいいみたいです。
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name('auth/charged.json', scope)
gc = gspread.authorize(credentials)
scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_dict(charged_dict, scope)
gc = gspread.authorize(credentials)
わかりづらいですがfrom_json_keyfile_nameがfrom_json_keyfile_dictになっています。
でcharged_dictというdict型の変数に(名前はなんでもよい)キーたちをセットし引数にしてやればいいとのことです。
charged_dictへのセットですが、そのダウンロードしたJSONってのはこんな感じなので
{
"type": "service_account",
"project_id": "charged-polymer-376***",
"private_key_id": "f822b1********************",
"private_key": "-----BEGIN PRIVATE KEY----- MIIEv******************,
"client_email": "google-sheet-263@charged-polymer-376204.iam.gserviceaccount.com",
"client_id": "115324932356144364298",
"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/google-shee",
"universe_domain": "googleapis.com"
}
このうち一部だけを環境変数にセットします。環境変数名はなんでもいいです。
(キー保存の常套手段として環境変数に入れておきましたが、中身が使えればなんでもいいです)
os.environ["GS_TYPE"] = "service_account"
os.environ["GS_PRIVATE_KEY"] = "-----BEGIN PRIVATE KEY-----\nMIIEm3YZhOzcW\nIV9qjoE4YuzXrs0Zdzq5Vn0=\n-----END PRIVATE KEY-----\n"
os.environ["GS_CLIENT_EMAIL"] = "google-sheet-263@charged-polymer-3******.iam.gserviceaccount.com"
os.environ["GS_PRIVATE_KEY_ID"] = "f822b1207797f********************"
os.environ["GS_CLIENT_ID"] = "11532493235614*******************"
※CloudFunctionsの場合は作成ウィザードの"ランタイム環境変数"でキーと値をセットして下さい
JSONの全てのキーが必要ではなく、SheetsAPI(gspreadでの利用)では
type、private_key、client_email、private_key_id、client_id
の5つのキーだけです。
ただこれはSCOPEにもよると思いますので、試しながらやってみてください。
(キーが足りないとエラーになります。)
後はdict型の変数(charged_dict)にセット。
charged_dict = {
"type":os.environ["GS_TYPE"],
"private_key":os.environ["GS_PRIVATE_KEY"],
"client_email":os.environ["GS_CLIENT_EMAIL"],
"private_key_id":os.environ["GS_PRIVATE_KEY_ID"],
"client_id":os.environ["GS_CLIENT_ID"]
}
これでfrom_json_keyfile_dict(charged_dict, scope) により接続が行えるようになります。
「まぁそりゃそうだろ」
って言う方もいるかと思いますが、私は目から鱗でした。
Firebase:dictをぶっこんで大丈夫
Firebase(Firestore)の認証は
cred = credentials.Certificate('auth/test-01.json')
app = firebase_admin.initialize_app(cred)
db = firestore.client()
こんな感じでCertificateの引数にJSONのパスを入れるのですが、これはdictを入れても勝手に判断してくれるようです。
なのでSheetsAPIと同じように環境変数へJSONの中身をセットしておき、認証時dict型変数に入れてCertificateの引数にします。ただSheetsAPIとキーが異なるので注意してください。
os.environ["FB_TYPE"] = "service_account"
os.environ["FB_PROJECT_ID"] = "test-01-fc****"
os.environ["FB_PRIVATE_KEY"] = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANB***************E KEY-----\n"
os.environ["FB_CLIENT_EMAIL"] = "firebase-adminsdk-******@test-01-.iam.gserviceaccount.com"
os.environ["FB_TOKEN_URI"] = "https://oauth2.googleapis.com/token"
charged_dict = {
"type":os.environ["FB_TYPE"],
"project_id":os.environ["FB_PROJECT_ID"],
"private_key":os.environ["FB_PRIVATE_KEY"],
"client_email":os.environ["FB_CLIENT_EMAIL"],
"token_uri":os.environ["FB_TOKEN_URI"]
}
cred = credentials.Certificate(charged_dict)
app = firebase_admin.initialize_app(cred)
db = firestore.client()
キーはtype、project_id、private_key、client_email、token_uriとなります。
(こちらもエラーになったら、確認してみてください。)
おわりに
この記事だけ読んだ方は「環境変数に入れたり出したり何がしたいの?」
って思われると思いますが、認証はJSONがいる というわけではないことがわかって頂ければ幸いです。
2023/9/22追記
すいません。以上の方法ですが、CloudFunctionsなど環境によってキー内の改行コード \n が \\n に変換されてしまう場合があり、その場合は以下の対処をお願いします。
cert["キー名"] = cert["キー名"].replace("\\n","\n")
特にprivate_keyが改行を含んでいるので、一度dictに入れた後にこの方法で変換してください。
※キーをdictにセットする際にPythonが勝手に \\n に変えてしまうようです。
参考にさせて頂いた記事