エラーメッセージ
Signature has expired
PyJWT (Python) — jwt.exceptions.ExpiredSignatureError
このエラーの意味
トークンの exp クレームが検証者の現在時刻より前であるため、PyJWT は(概念上は)署名のチェックに成功した後でトークンを拒否しました。この表現は少し誤解を招きます。署名そのものが期限切れになるわけではなく、トークンの有効期間が終わったということです。
発生する理由
有効期間を過ぎた古いトークンを再利用している
よくある長時間動作するワーカー内でキャッシュされた認証情報、クライアントサイドに保存されて TTL を過ぎてから再送されたトークン、数日前に生成したフィクスチャのトークンを使う結合テストなどが該当します。この拒否は正しい動作です。
検証するホストの時計がずれている
よくある時計が進んでいる検証者では、すべてのトークンの実質的な有効期間が短くなります。よくある原因は、時刻同期のないコンテナーや CI ランナーです。わずか 60〜120 秒のずれでも、有効期間の短いトークンが断続的に失敗します。
発行者が exp を誤って書き込んでいる
ときどきexp はエポックからの整数の秒数でなければなりません。誤ってシリアライズされた datetime オブジェクトやミリ秒の値を渡すと、発行した時点ですでに期限切れのトークンができます。その場合は検証が毎回すぐに失敗するので、通常の期限切れと区別できます。
タイムゾーン情報のない datetime で exp を生成している
ときどきdatetime.now(timezone.utc) ではなく datetime.now()(タイムゾーン情報のないローカル時刻)から exp を作る発行者は、すべてのトークンの有効期間をサーバーの UTC オフセット分だけずらしてしまいます。発行者が UTC のどちら側にあるかによって、トークンは数時間早く期限切れになるか、本来の TTL を気づかれないまま超えて有効であり続けます。PyJWT はタイムゾーン情報のある datetime を正しくエンコードします。落とし穴になるのは、情報のない datetime です。
修正方法
- 1.
わずかなずれを leeway で吸収する
leeway パラメーター(秒)は、exp と nbf のチェックに適用されます。多くても数十秒にしてください。それより大きい値は、気づかないうちにトークンの有効期間を延ばしてしまいます。
python import jwt payload = jwt.decode( token, key, algorithms=["ES256"], leeway=30, # seconds of tolerated clock skew ) - 2.
実際のタイムスタンプを確認する
検証なしでクレームを読み、exp をホストの時計と比較してください。差の大きさから、期限切れなのか、時刻のずれなのか、発行時のバグなのかがわかります。
python import jwt, time claims = jwt.decode(token, options={"verify_signature": False}) print("exp:", claims["exp"], "now:", int(time.time()), "delta:", int(time.time()) - claims["exp"], "s")