みなさんこんにちは!ネットワークの深淵とプロトコルのロマンを愛するインフラエンジニアの私です。
日々のシステム開発やインフラ構築で、Web APIを叩いたり、認証トークンとしてJWTという言葉を見かけたりすることはありませんか?「なんだか難しそうな暗号の塊みたいだな……」「ヘッダーとかペイロードとか、英語がいっぱいで目が回りそう!」と感じている初学者の方も多いのではないでしょうか。
でも、安心してくださいね。実はJWTの仕組みは、私たちが普段何気なく使っている「封筒に入った手紙」や「カチッと施錠された郵便ポスト」とまったく同じ原理で動いているんです。
今回は、パケットの世界から少し離れて、現実世界の身近な例えを交えながら、JWTが持つ3つのセグメントと「改ざんを防ぐ魔法(署名)」の仕組みを、一歩ずつ優しく紐解いていきましょう!
—
1. JWTってそもそも何だろう?現実の「手紙」に例えてみよう
JWTは「JSON Web Token」の略称です。なんだか硬い名前ですが、一言で言えば「サーバーとブラウザの間で、身分証や大事なメッセージを安全にやり取りするためのデジタル手紙」だと思ってください。
例えば、あなたがテーマパークに入園するときを想像してください。
最初にチケット売り場で身分証明書を見せると、スタッフさんが「この人は入場券を持っていますよ」という証明として、特別なスタンプが押されたフリーパスポートを首から下げさせてくれますよね。
アトラクションに乗るたびに、いちいち身分証を最初から見せる必要はありません。首から下げたパスポートを見せるだけで、スタッフさんは「あ、この人は本物の入場者だな」と一目で分かります。
Webの世界でも全く同じです。ユーザーがログインしたあと、サーバーは「この人は〇〇さんです」という情報を詰め込んだJWTを発行します。ブラウザはそのトークンを大事に持っておき、APIにアクセスするたびにサーバーへ提示することで、毎回パスワードを聞かれることなくスムーズに通信できるというわけですね。
—
2. ドットで区切られた3つのピースの正体
それでは、実際のJWTがどんな形をしているか見てみましょう。
通信のログなどを覗くと、JWTは次のような、ドット(.)で3つに区切られた長い文字列として流れていきます。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
「うわっ、暗号化されていて読めない!」と思いましたか?
実はこれ、暗号化されているわけではありません。 ただの「お化粧(エンコード)」がされているだけなんです。郵便配達の例えで言えば、宛先や中身の書類を専用のフォーマットで綺麗に書き直してある状態です。
この3つのパーツ(セグメント)を、左から順に分解して見ていきましょう!
第1セグメント:ヘッダー(Header)
これは、いわば「手紙の封筒の表書き」です。
「この手紙はどのルール(アルゴリズム)を使って封をしていますか?」という情報が書かれています。
{
"alg": "HS256", // 署名に使っている暗号化のアルゴリズム(種類)
"typ": "JWT" // トークンの種類(JWTだよという宣言)
}
第2セグメント:ペイロード(Payload)
これが、封筒の中に入っている「手紙の本文(中身のデータ)」です。
ここにユーザーの名前やID、権限、有効期限などの大切なデータがJSON形式で詰め込まれています。
{
"sub": "1234567890", // ユーザーの固有ID
"name": "Jane Doe", // ユーザーの名前
"iat": 1516239022 // このトークンを発行した日時(タイムスタンプ)
}
第3セグメント:署名(Signature)
そして一番重要で、ドラマの鍵を握るのがこの3つ目のパーツ、「署名(シグネチャ)」です。
これは、手紙の封じ目に押された「絶対に偽造できない差出人のワックスシール(封蝋)」だと思ってください。
—
3. なぜ改ざんできないの?署名の仕組みを徹底解剖
さて、ここで鋭い読者ならこう思うはずです。
「あれ? 第1セグメントのヘッダーも、第2セグメントのペイロードも、ただのJSONをBase64URLという形式で文字列に変換しているだけなんでしょ? じゃあ、誰でも中身を書き換えて、自分を『管理者権限』に偽装できちゃうんじゃないの?」
その通り!エンコードされているだけなら、悪意あるユーザーがペイロードの数値を勝手に書き換えて、再びBase64URLに変換して送信し直すことは技術的に可能です。
そこで登場するのが、第3セグメントの「署名(Signature)」なんです。
郵便配達と「割印(わりいん)」の魔法
想像してみてください。
重要な契約書を封筒に入れ、その糊付け部分に、発行者(サーバー)だけが持っている「特別なハンコ」でガッツリと割印を押しました。
もし、途中で悪意ある第三者が封筒を無理やり開けて、中身の金額を「1万円」から「1000万円」に書き換えたとしましょう。
このとき、一度剥がしてしまった封筒の糊付けや割印は、元の綺麗な状態に戻せるでしょうか? もちろん、元通りには絶対に直せませんよね。
JWTの署名は、これとまったく同じことをデジタルで行っています。
署名が作られる仕組み
サーバーは、署名を作るときに次のような計算(ハッシュ化)を裏側で行っています。
1. 第1セグメント(ヘッダー)の文字列
2. 第2セグメント(ペイロード)の文字列
3. サーバーだけが秘密裏に持っている「秘密鍵(シークレットキー)」
これら3つをガチャンコと混ぜ合わせて、専用の数学的関数(HMAC-SHA256など)に通すことで、第3セグメントの「署名」を作り出します。
署名 = 秘密の計算方法( ヘッダー + ペイロード + サーバーだけの秘密の合言葉 )
もし、悪意あるユーザーが第2セグメントのペイロードをほんの1文字でも書き換えたとします。
すると、サーバーが後から「ヘッダー + 書き換えられたペイロード + 秘密の合言葉」で再度計算したときの結果と、送られてきた偽物の署名がピタリと一致しなくなるのです!
サーバーは「おや? この手紙、途中で誰かに中身を書き換えられたな、あるいは偽物だな!」と一瞬で気づき、アクセスをピシャリと拒否することができます。これが、JWTが改ざんを完全に検知できる仕組みです。
—
4. 実務で触れる!PythonでJWTの生成と検証を体験しよう
理屈が分かったところで、今度は実際にコードを動かしてみましょう。
今回は、インフラやバックエンド開発でよく使われるPythonを使って、実際にJWTを作り、検証するシンプルなサンプルコードを見てみます。
PythonでJWTを扱う際は、定番のライブラリであるPyJWTを使用します。事前にターミナル等でインストールしておいてくださいね。
# ターミナルでのインストールコマンド
pip install PyJWT
それでは、Pythonスクリプトの実装例です。コメントを丁寧に書きましたので、上から順番に追ってみてください。
import jwt
import datetime
# 【1】サーバーだけが知っている秘密の合言葉(シークレットキー)
# この秘密鍵が外部に漏れると、誰でも偽の署名が作れてしまうため厳重に管理します!
SECRET_KEY = "super-secret-infra-key-do-not-share"
def create_and_verify_jwt():
# --- 【2】JWTの生成(発行)プロセス ---
# ペイロード(手紙の本文に含めるデータ)を定義します
payload = {
"sub": "user_001",
"name": "インフラ太郎",
"role": "administrator",
# トークンの有効期限(現在時刻から1時間後)を設定
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
# ヘッダーとペイロード、そして秘密鍵を組み合わせてJWTトークン(署名付き)を生成します
# アルゴリズムには標準的な "HS256" を指定しています
encoded_jwt = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("=== 生成されたJWTトークン ===")
print(encoded_jwt)
print("\n")
# --- 【3】JWTの検証(改ざんチェック)プロセス ---
try:
# クライアントから送られてきたトークンを、同じ秘密鍵を使ってデコード(検証)します
# ここでペイロードが書き換えられていたり、有効期限が切れているとエラーが発生します
decoded_payload = jwt.decode(encoded_jwt, SECRET_KEY, algorithms=["HS256"])
print("=== 署名検証成功!中身のデータはこちらです ===")
print(decoded_payload)
except jwt.ExpiredSignatureError:
print("エラー: トークンの有効期限が切れています。再ログインしてください。")
except jwt.InvalidSignatureError:
print("【警告】署名が一致しません!このトークンは途中で改ざんされています!")
if __name__ == "__main__":
create_and_verify_jwt()
このコードを実行すると、先ほど解説した3つのセグメントを持ったトークンが生成され、正しく秘密鍵が一致すれば中身のデータが安全に読み取られる様子が確認できます。もし途中でpayloadのデータを書き換えて検証しようとすると、InvalidSignatureErrorがキャッチされ、不正なアクセスを防いでくれるはずです。
—
5. まとめ:安全なAPI設計への第一歩
今回は、JWTのヘッダー、ペイロード、そして署名構造について、現実世界の郵便配達や封印の例えを交えてお話ししました。
- ヘッダーは「封筒の表書き(アルゴリズムの指定)」
- ペイロードは「手紙の本文(ユーザー情報など)」
- 署名は「絶対に偽造できないワックスシールの割印(改ざん検知の要)」
一見すると難解に見えるプロトコルやデータ構造も、その背後にある「現実世界のルール」に置き換えてみると、驚くほどスッと頭に入ってきますよね。
インフラやセキュリティの基本は、こうした「信頼をどう担保するか」という技術の積み重ねです。今回の知識が、みなさんの日々の開発やインフラアーキテクチャ設計の小さなヒントになれば、ライターとしてこれ以上の喜びはありません。
それでは、また次回のプロトコルの深淵でお会いしましょう!
コメント