【入門編】 JWTの有効期限(exp)と発行時刻(iat)の検証によるリプレイ攻撃対策 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

こんにちは!Webアプリケーションのセキュリティ対策、毎日お疲れ様です。新人のIT担当者の方や、「セキュリティってなんだか難しそう……」と不安を感じている開発者の方にとって、認証の仕組みを正しく理解するのはひと苦労ですよね。

でも、安心してください!一歩ずつ、身近な例えから紐解いていけば、誰でも確実にセキュアなシステムを作れるようになります。

今回は、現代のWeb開発で避けて通れないJWT(JSON Web Token)、そしてその中でも特に重要な有効期限(exp)と発行時刻(iat)を使ったリプレイ攻撃対策について、詳しく解説していきます。

—

家の鍵から学ぶ「JWT」と「リプレイ攻撃」の正体

まずは、JWTがどんなものか、そして攻撃者が何を企んでいるのかを、私たちの身近にある「家の鍵」に例えて考えてみましょう。

1. JWTは「デジタル身分証付きの合鍵」

Webサイトにログインしたとき、サーバーはあなたに「この人は認証済みですよ」という証明書を発行します。これがJWTです。
JWTの中には、あなたのユーザーIDや、「いつ作ったか(iat)」、そして「いつまで有効か(exp)」という情報が書かれています。さらに、サーバーだけが知っている秘密のハンコ(デジタル署名)が押されているため、中身を勝手に書き換えることはできません。

2. 「リプレイ攻撃」ってなに?

さて、ここで「リプレイ(再利用)攻撃」という悪だくみを考えてみましょう。

あなたが高級ホテルに泊まるとします。フロントで電子カードキーを受け取り、部屋に入りました。もし、悪意ある泥棒が、あなたがホテルの廊下で落とした(あるいは通信を盗聴してコピーした)古いカードキーを拾ったらどうなるでしょうか?

もしそのカードキーに「有効期限」が設定されていなかったら、泥棒はいつでも、何日経ってでも、その部屋に自由に出入りできてしまいますよね。これが、ネットワークの世界における「リプレイ攻撃」です。攻撃者は、過去に正しく通信された古いJWTを盗み見し、何度もサーバーに送りつけることで、あなたになりすまして不正アクセスを試みます。

この脅威を防ぐために絶対に必要なのが、JWTの有効期限(exp)と発行時刻(iat)の正しい検証なのです。

—

JWTの心臓部:exp と iat のパラメータを覗いてみよう

JWTは、一般的に Header、Payload、Signature という3つのパーツがドット(.)で繋がった文字列です。この中の Payload(中身のデータ部分)に、私たちが注目すべきタイムスタンプが含まれています。

実際のPayloadのデータ構造をJSON形式で見てみましょう。

{
  "sub": "user_12345",     // ユーザーの識別子(誰のトークンか)
  "name": "山田 太郎",       // ユーザーの名前
  "iat": 1717152000,       // 発行時刻 (Issued At)
  "exp": 1717155600        // 有効期限 (Expiration Time)
}

ここで使われている数字は、UNIX時間(1970年1月1日からの経過秒数)です。

  • iat (Issued At): このトークンが作られた正確な時間。
  • exp (Expiration Time): このトークンが「ゴミ箱行き」になる、つまり失効する時間。

防犯の観点から言えば、「発行されてからほんの数分、あるいは数時間だけ使える使い捨ての合鍵」を作ることが、リプレイ攻撃を防ぐ最大の秘訣になります。

—

実装でハマる盲点!サーバー側での検証ロジック

「よし、トークンに exp を入れておけば安心だね!」……と油断してはいけないのが、セキュリティの厳しいところです。

実は、多くのライブラリは自動で exp を見てくれますが、「本当にその検証が正しく行われているか」「時計のズレを考慮しているか」を開発者自身が理解していないと、重大な脆弱性を残してしまいます。

ここからは、Node.js(Expressとjsonwebtokenライブラリ)を例に、安全な検証ロジックの書き方を見ていきましょう。一歩ずつ、コードのコメントを読みながら確認してくださいね。

実装コード例(Node.js / JavaScript)

const jwt = require('jsonwebtoken');

// サーバーだけが知っている秘密鍵(絶対に外部に漏らさないこと!)
const SECRET_KEY = 'my_super_secret_key_which_is_safe';

/**
 * クライアントから送られてきたJWTを検証する関数
 * @param {string} token - リクエストヘッダーから取得したJWT
 */
function verifyUserToken(token) {
    try {
        // jwt.verifyを使うことで、署名の正当性だけでなく
        // exp(有効期限)の切れ具合も自動的にチェックしてくれます!
        const decoded = jwt.verify(token, SECRET_KEY, {
            // clockTolerance: サーバー間の時計の微妙なズレ(例: 5秒以内)を許容する設定
            clockTolerance: 5 
        });

        console.log('認証成功!ユーザーID:', decoded.sub);
        return { success: true, data: decoded };

    } catch (err) {
        // 有効期限切れの場合、ここでエラーをキャッチします
        if (err.name === 'TokenExpiredError') {
            console.warn('警告: 期限切れの古いトークンが使われました(リプレイ攻撃の可能性)');
            return { success: false, message: 'トークンの有効期限が切れています。再ログインしてください。' };
        }
        
        // 署名がおかしい、改ざんされている場合
        console.error('エラー: 不正なトークンです:', err.message);
        return { success: false, message: '認証に失敗しました。' };
    }
}

ここが現場の泥臭いポイント!

教科書には「jwt.verify を使えば exp は勝手にチェックされます」と書いてありますが、現場のシステムでは次のような「落とし穴」があります。

1. サーバーの時計が狂っている問題
NTP(時刻同期)を設定していないサーバー同士だと、数秒〜数分のズレが生じます。発行したばかりのトークンが「未来のトークン」と判定されてエラーになったり、逆に期限切れが検知できなかったりします。本番環境では必ずサーバーの時刻同期を確認しましょう。
2. iat(発行時刻)の未来チェック
攻撃者が巧妙にシステム時間を操作して、未来の iat を持つトークンを作ろうとするケースがあります。厳格なシステムでは、iat が現在時刻よりあまりにも未来になっていないか(あるいは過去すぎてセッション管理のポリシーに反していないか)を追加でチェックすることもあります。

—

まとめ:今日からできるセキュリティファーストの一歩

今回は、JWTの有効期限(exp)と発行時刻(iat)に焦点を当て、リプレイ攻撃を防ぐ仕組みを解説しました。

  • JWTは「期限付きの合鍵」であると意識する。
  • exp(有効期限)を必ず短めに設定し、長すぎる寿命を与えない。
  • ライブラリの検証機能(jwt.verify等)に頼るだけでなく、エラーハンドリングを丁寧に行い、期限切れのトークンを確実に弾く。

セキュリティは、一度設定して終わりではなく、日々の運用や小さな気配りの積み重ねです。難しく考えず、まずはご自身のプロジェクトのJWT生成・検証部分で、「exp はちゃんと入っているか?」「有効期限は長すぎていないか?」を確認することから始めてみてくださいね。

あなたの開発するWebアプリケーションが、より安全で信頼されるものになるよう応援しています!

コメント

タイトルとURLをコピーしました