ja ja en nb ja en ja zh ja ja ja ja ja ja ja ja ja zh ja ja ja ja ja ja zh ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja ja ja ja en ja en ja ja ja en ja en zh ja en ja ja ja ja ja ja ja ja ja ja ja ja ja en ja ja ja ja ja ja en ja ja ja ja ja pl nl ja de ja ja de ja ja nb ja ja pl en ja nb ja ja ja ja ja en ja ja ja ja ja ja ja ja ja ja ja ja ja ja ja jbo en en ja nl nb en ko it en it it it en fr it ja ja en la fi ja it it it pt it de en ja en en it fr it en it nb it ja ja ja ja en ja ja ja ja ja ja ja ja ja
AWS LambdaからGoogle APIを呼び出す - Sanwa Systems Tech Blog
APIが選択できたら「認証情報」から「認証情報を作成」をクリックし、「OAuthクライアントID」を選択しましょう。
OAuth2.0のおさらい
ここで簡単にOAuth2.0のおさらいをします。一般にWebサービスが提供するOAuth認証を利用するアプリケーションは、
ユーザーが許可した権限でWebサービスのAPIを利用することができる
APIを呼び出すときには、認証時に発行されたキーをリクエストに含める(ユーザーのIDやパスワードは分からない)
このキーには有効な寿命が設定されていることがある
有効期限がある場合、認証時に与えられたリフレッシュトークンを使ってリクエスト時に更新しなければならない
定期的に更新しても構わない
という流れなのでした(そしてGoogleのOAuth認証では寿命が設定されています)。これを念頭に次のステップへ進みます。
OAuthクライアントIDを作るためにはまず「同意画面」の設定が必要となります。これはユーザーに「このアプリケーションに権限を与えますか?」という確認を提示するための画面で、ユーザーがその可否を判断するために必要な連絡先やアプリケーション名を記入しておきます。
最後に「アプリケーションの種類」として「その他」を選んで「作成」をクリックすれば完了です。その場でクライアントIDとクライアントシークレットが発行されます。これは後からでも確認できるのでメモを取る必要はありません。
認証に使うアカウントでGoogleにログインした状態で、次のURLをブラウザから開きます(改行と空白は削除して1行にしてください)。
https://accounts.google.com/o/oauth2/v2/auth ?scope=https://www.googleapis.com/auth/gmail.readonly%20https://www.googleapis.com/auth/gmail.labels &redirect_uri=urn:ietf:wg:oauth:2.0:oob &response_type=code &client_id=999999999999-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
scopeにはこのアプリケーションが要求するスコープ(下述)をスペース(%20)区切りで並べます。
redirect_urlは固定値です。ここでは認証後に外部へは遷移せず、認証コードを表示する画面を表示することを指示しています。詳しくはドキュメントを参照してください。
client_idには最初に取得したクライアントIDを書きます。
approval_prompt, access_typeを指定すると書いてある記事も多くありますが、現時点では必要ないようです。
スコープは様々な権限を適当な粒度で丸めたものです。たとえばメールを読み取る権限が必要なだけならhttps://www.googleapis.com/auth/gmail.readonlyと書けば良いのですが、メールにラベルを追加・削除したければhttps://www.googleapis.com/auth/gmail.labelsも指定しなければなりません。Gmailについてのスコープはドキュメントに一覧があります。
このURLを開くと、画面には権限のリクエストを許可するか拒否するかの選択肢が表示されます。
要求されている権限は確かにスコープで指定したものであることが分かります。ここで「許可」を押すと秘密のコードが表示されます(これはWebページのタイトルにも出力されています)。このコードこそが、ユーザーがアプリケーションに権限を与えたという証拠になります。
このコードを含めて再度リクエストを送るとaccess tokenとrefresh tokenが取得できます。curlで次のコマンドを叩きます。
curl -d client_id=999999999999-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com \ -d client_secret=xxxxxxxxxxxxxxxxxxxxxxxx \ -d grant_type=authorization_code \ -d redirect_uri=urn:ietf:wg:oauth:2.0:oob \ -d code=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ https://accounts.google.com/o/oauth2/token
client_id, client_secretはプロジェクト作成時に貰った値、codeは今ブラウザに表示された値です。うまく行けばJSON形式で結果が返ります。
{ "access_token" : "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "token_type" : "Bearer", "expires_in" : 3600, "refresh_token" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }
欲しかったaccess tokenとrefresh tokenが取得できました!
権限を手に入れたので、後はAPIを好きに叩くことができます。GmailのAPI一覧はドキュメントにありますので、たとえばこのコマンドではGmailで作成済みのラベル一覧を出力します。エンドポイントの中にあるuserIdはmeに置き換えておくのを忘れないでください。
curl -H "Authorization: Bearer xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ https://www.googleapis.com/gmail/v1/users/me/labels
同様に、今年1/1以降で「猫」が含まれるメールを検索し、そのメールIDのリストを取得するのならこうなります。スペースはエスケープしてください。またqの引数にはGmailの検索クエリと全く同等のものが使えます。
curl -H "Authorization: Bearer xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ https://www.googleapis.com/gmail/v1/users/me/messages?q="after:2016-01-01%20猫"
access token更新
ここで最も重要な値は上で得たrefresh tokenです。これさえあればいつでも新しいaccess tokenを取得(=更新)できるからです。これはアプリケーション側で保存しておく必要があります。
curl -d client_id=999999999999-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com \ -d client_secret=xxxxxxxxxxxxxxxxxxxxxxxx \ -d refresh_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ -d grant_type=refresh_token \ https://accounts.google.com/o/oauth2/token
更新を行ったとしても、更新前に使っていたaccess tokenは有効なまま残ります。つまり、
何か処理を行う前に必ず更新するようにする
refresh tokenさえ保存しておけばいい。ここで得たキーは1時間ずっと使える
N分ごとに自動更新するようにする(Nは30分、55分など)
access tokenをDBか何かに入れておく
N分ごとにcronなどでrefresh tokenを使ってaccess tokenを取得し、DBの中のaccess tokenを更新する
アプリケーションはDBから取得したaccess tokenで処理をする。このaccess tokenは最短(60-N)分で無効になる
といった選択肢ができることになります。自動処理ができるなら後者の方が簡単そうですね。
トークンをDynamoDBに入れる
access tokenとrefresh tokenをDynamoDBに入れておきましょう。各種外部サービス*1のAPIを叩くためのキーを管理するためのテーブルを元から作ってあったので、そこに追加することにしました。このテーブルのプライマリキーはkeyにしています。
更新用Lambdaを用意する
どこかのサーバーからcronでバッチを回してもいいのですが、ここはLambdaで作って自動実行することにします。何をやっているかを明確にするためにライブラリは使っていません。
https://github.com/nurenezumi/ssc-techblog/blob/master/node.js/refresh.jsgithub.com
このコードを実行すると、DynamoDBの指定したテーブル・レコードからrefresh tokenなどの値を取得してaccess tokenを更新し、レコードとタイムスタンプを更新します。DynamoDBへのアクセス権を与えなければならないことには気をつけましょう。
作成したLambdaを再度開き、Event sources>Add event sourceと進んで、表示されるダイアログでEvent source typeとしてCloudWatch Events - Scheduleを選択します。
Schedule expressionとして「rate(30 minutes)」を選べば30分ごとに自動実行してくれるようになります。簡単ですね。
トークンを使って何かする
これで常に有効なaccess tokenがDynamoDBに入っている状態となりました。先ほどと同じように、Lambdaから今あるラベルの一覧を取得してログに出力してみます。
var https = require("https"); var querystring = require("querystring"); var AWS = require("aws-sdk"); var dynamo = new AWS.DynamoDB.DocumentClient(); var TABLENAME = "YOUR-DYNAMODB-TABLE-NAME"; var KEYNAME = "YOUR-RECORD-KEY"; var getGoogleApiKey = function(callback) { var condition = { TableName : TABLENAME, KeyConditionExpression : "#k = :val", ExpressionAttributeValues : {":val" : KEYNAME}, ExpressionAttributeNames : {"#k" : "key"} }; dynamo.query(condition, function(err, data) { if (err) { context.fail(err); } else { callback(data.Items[0].token); } }); }; var kickGet = function(path, token, success, fail) { var options = { hostname: "www.googleapis.com", port: 443, path: path, method: "GET", headers : { "Authorization" : "Bearer " + token } }; var req = https.request(options, function(res) { body = ""; console.log("[DEBUG] response received"); res.setEncoding("utf8"); res.on("data", function(chunk) { body += chunk; }); res.on("end", function() { if (res.statusCode == 200) { console.log(body); var json = JSON.parse(body); success(json); } else { console.error("ERROR!"); console.error(body); console.error(res.errorMessage); fail(res); } }); }); req.on("error", function(err) { fail(err); }); req.end(); }; exports.handler = function(event, context) { getGoogleApiKey(function(token) { kickGet( "https://www.googleapis.com/gmail/v1/users/me/labels", token, context.succeed, context.fail ); }); };
結果はこんな感じです。無事出力に成功しました。
OAuthのフロー図を見ると若干面倒くさいように感じられますが、実はこれだけのコードで実現可能なシンプルな処理で構成されていることが分かります。これを使って次はもっと複雑なことにチャレンジしてみたいと思います*2。それでは!
*1:Google、Slack、Salesforceなど
*2:実装よりも記事を書く方が大変だったりしますが
nurenezumi 2016-04-28 20:25
S3 + AWS Cognito + Google認証でドメイン制限のついた非公開サイトを作る
こんにちは、AWS担当のwakです。 弊社では全社的にG Suiteを導…
Analytics の内容を Slack でグラフ表示
こんにちは、久しぶりに和朗です。 サービススタート時など、Go…
API Key認証付きのAPI GatewayからS3へリダイレクトしたい(できない)
こんにちは、AWS担当のwakです。 概要 表題の通りです。できな…
API Gateway + LambdaでSlackのCustom Commandを作る
こんにちは、AWS担当のwakです。先日このような記事を書いたの…
AWS LambdaのパラメータをDynamoDBで管理する
こんにちは、AWS担当のwakです。前回に続きAWS Lambdaの話をし…
« Slackを使ってバグ管理ィィィィィ Google Apps APIで新入社員対応(ユーザー… »