TT Lab
はじめる
学ぶ 学習パス コース

統合とデプロイ

夜間バッチが毎回いくつか 401 で落ちる

TT Labで続きを見る

目標

短い寿命のアクセストークンを持って、他社のAPIを呼び続けるクライアントを作ります。期限が切れる前にあらかじめ更新し、時計のずれの余裕を置き、401を1回だけ受け取って、更新後に再試行し、ワーカーが複数いても、更新が1回だけ起きるようにします。

なぜ重要なのか

「夜間バッチが毎回2、3件ずつ401で失敗します」の原因は、ほとんどの場合、同じです。トークンを一度受け取っておいて、401が来たらそのとき新しく受け取る構造は、期限が切れる瞬間ごとに、必ず1回は失敗します。 トークンが短く生きるのは、欠陥ではなく設計です。寿命が短ければ、漏れたときに使える期間が短くなります。そのため、直すべきなのは、サーバーではなく、こちらのクライアントです。 そして、直すときは、3つを一緒に見る必要があります。時計は正確に同じではないので、余裕が必要で、401がトークンの問題ではないこともあるので、再試行の回数に上限が必要で、ワーカーが複数いると、期限が切れる瞬間に更新が一度に押し寄せるので、更新を1回にまとめる必要があります。 採点ツールは、作成した文章を信じません。認証サーバーを、採点ツールが選んだポートで直接起動し、発行の回数とリソース呼び出しの回数を、サーバー側で数えて、作成したクライアントが、実際に何回更新し、何回かけ直したかを、突き合わせます。

ステップ

  1. /root/token/authsrv.pyを作成してポート8015で起動し、トークンを1つ受け取って、/root/token/token.jsonに保存してください。
  2. /root/token/decode.pyを作成して、トークンの中のヘッダーとペイロードを読み、/root/token/claims.jsonに書いてください。
  3. /root/token/should_refresh.pyを作成して、期限切れ・まだ有効でない・期限間近・正常の4つの分岐を、時計のずれの余裕まで考慮して見分けるようにしてください。
  4. /root/token/client.pyを作成して、期限が切れる前にあらかじめ更新しながら、何度呼び出しても、401が1件も出ないようにしてください。
  5. client.pyが401を受け取ったら、更新後にたった1回だけかけ直すようにしてください。
  6. client.pyが、ワーカーが複数同時に始まっても、トークンの発行が1回だけ起きるようにしてください。
  7. /root/token/token_policy.jsonに、ポリシー表を書いてください。
  8. /root/token/token_report.mdに、4つの節で報告してください。

参考

短く生きるトークンを受け取ってみる

/root/token/authsrv.pyを作成してポート8015で起動し、POST /oauth/tokenでトークンを1つ受け取って、応答の全体を/root/token/token.jsonに保存してください。

JWTは、base64urlでエンコードしたヘッダー・ペイロードと、HMAC署名を、ドットでつないだものです。Pythonの標準ライブラリのhmac・hashlib・base64だけで作れます。発行の数とリソース呼び出しの数をサーバーが数えておけば、あとでクライアントが実際に何回更新したかを確認できます。

トークンの中を開いてみる

/root/token/decode.pyを作成して、--inで受け取ったトークン応答からJWTを取り出してヘッダーとペイロードを読み、--outで/root/token/claims.jsonに、{"header": ..., "payload": ..., "ttl_s": exp - iat}を書くようにしてください。

JWTの前の2つの断片は、暗号ではなく、base64urlでエンコードされた平文です。base64urlはパディング(=)を外して流通するので、デコードの前に、長さを4の倍数に合わせる必要があります。ここですることは、読み取りであり、検証ではないという点を、覚えておいてください。この値を根拠に権限を判断してはいけません。

いつ更新すべきか

/root/token/should_refresh.pyを作成して、--exp・--now・--skew-s・--nbfで判定するようにしてください。判定の順序は、nbf、期限切れ、期限間近で、reasonは、not_yet_valid・expired・near_expiry・okです。期限間近のトークンは、まだ使えるので、validはtrueです。

時計は正確に同じではありません。nbfは、こちら側に余裕を与えて見てこそ、受け取ったばかりのトークンが「まだ有効ではない」として拒否されません。期限間近の判定は、남은 시간 <= 여유(韓国語で「残り時間 <= 余裕」を意味する式です)の1行で済みます。remaining_sは、期限が切れたあとは負の数になる必要があります。

期限が切れる前にあらかじめ更新する

/root/token/client.pyを作成して、--calls回リソースを呼びつつ、毎回トークンが期限間近かを確認し、期限間近であれば、先に更新するようにしてください。トークンの寿命より長い時間にわたって呼び出しても、unauthorizedが0である必要があります。

前のステップの判定器をモジュールとして読み込んで使えば、ルールが1か所にだけ置かれます。トークンはファイルに入れておき、呼び出しの直前に「これを使ってよいか」を問うてください。寿命3秒のトークンで3秒を超えて呼び出せば、更新が実際に起きたかを、サーバーの発行の数で確認できます。

401は1回だけ受け取る

client.pyが401を受け取ったら、更新して、たった1回だけかけ直すようにしてください。更新しても401が続くなら、その呼び出しは失敗として終える必要があります。--start-expiredで、期限切れのトークンを持って始められる必要があります。

権限が足りない状況に、401を返すサーバーが多いです。そうしたサーバーで、再試行の回数に上限がなければ、更新と401が終わりなく繰り返されて、認証サーバーを叩き続けます。リソースの呼び出しが、呼び出し1回につき、ちょうど2回だけ出ているかを、サーバーの呼び出しの数で確認してください。

期限が切れる瞬間に、20個が一度に更新する

client.pyが、--workersで同時に複数の呼び出しを行っても、トークンの発行が1回だけ起きるようにしてください。キャッシュファイルとロックを使い、ロックを取ったあとに、キャッシュをもう一度確認する必要があります。

ロックだけかけても、更新が順番に並んで起きるだけで、回数はそのままです。待っている間に、ほかのワーカーがすでに更新しているかもしれないので、ロックを取ったあとにキャッシュを再び読んで、使えるトークンがあれば、それを使ってください。Pythonでは、fcntlのファイルロックを使います。

ポリシー表で固定する

/root/token/token_policy.jsonに、token_ttl_s・refresh_skew_s・max_retry_on_401・cache_path・stampede_guard・clock_sync・notesを書いてください。refresh_skew_sは1以上で、寿命より小さい必要があり、max_retry_on_401は1以下です。

値の根拠を、notesに1行で残してください。余裕を寿命の何パーセントにしたか、なぜ再試行を1に縛ったかが入っていれば十分です。この表は、あとでパートナーが寿命を変えたときに、こちらが何を一緒に変えるべきかを知らせる文書でもあります。

トークンの点検報告書

/root/token/token_report.mdに、## 토큰이 어떻게 생겼나、## 언제 갈아야 하나、## 401 을 받으면 무엇을 하나、## 워커가 여럿일 때(韓国語の見出しで、順にトークンがどんな形か、いつ更新すべきか、401を受け取ったら何をするか、ワーカーが複数のとき、を意味します)の4つの節で書いてください。claims.jsonとtoken_policy.jsonの値が、本文に入っている必要があります。

読む人は、夜間バッチがなぜ401で死ぬのかを尋ねる人です。「トークンが期限切れになったから」で終わらせず、期限切れは正常であり、問題は、こちらが期限切れを扱う方法だったという点を、数字で見せてください。