JWTの構造(Header, Payload, Signature)を初心者向けに徹底解説!
新人
「先輩、JWTって聞いたことはあるんですが、構造については全然わかりません。」
先輩
「JWTはJSON Web Tokenの略で、認証や認可に使われる安全なトークン形式だよ。その中身は3つの部分に分かれていて、Header・Payload・Signatureから構成されているんだ。」
新人
「3つの部分それぞれには、どんな役割があるんですか?」
先輩
「それぞれが大事な役割を持っているよ。Headerはトークンの種類や署名のアルゴリズム、Payloadはユーザー情報や権限、Signatureは改ざん防止のための署名だね。」
新人
「なるほど、ではもっと詳しく教えてください!」
1. JWTとは?(基本的な説明と構造の概要)
JWT(JSON Web Token)は、WebアプリケーションやAPI通信でユーザーの認証情報や権限情報を安全にやり取りするためのトークンです。JSON形式でデータを表現し、それをBase64URL形式でエンコードして署名を付与します。
この仕組みにより、サーバー側にセッション情報を保持せずともユーザーの状態を確認できる「セッションレス認証」を実現できます。これは大規模システムや複数サーバー構成(スケーリング構成)で特に有効です。
JWTは「Header」「Payload」「Signature」の3つの部分から構成され、それぞれがドット(.)で区切られています。
例えば、JWTの文字列は次のようになります。
xxxxx.yyyyy.zzzzz
この例ではxxxxxがHeader、yyyyyがPayload、zzzzzがSignatureに該当します。
JWTが注目される理由
従来のセッション管理はサーバー側に情報を保存するため、スケーラビリティに制限がありました。しかし、JWTはトークン自体に必要な情報を持たせるため、どのサーバーでも同じトークンを検証でき、セッション情報を共有する必要がありません。また、オープンスタンダードであるため、異なる言語やフレームワーク間でも利用できます。
2. JWTの3つの構成要素の概要(Header・Payload・Signature)
JWTは3つの要素で成り立っています。それぞれの役割を理解すると、トークンの構造を読み解けるようになります。
Header(ヘッダー)
Headerにはトークンの種類(typ)と署名アルゴリズム(alg)が格納されます。例えば以下のようなJSON構造になります。
{
"alg": "HS256",
"typ": "JWT"
}
これをBase64URLでエンコードしたものがJWTの先頭部分になります。
Payload(ペイロード)
Payloadには「クレーム」と呼ばれるデータが含まれます。クレームは、ユーザー情報や権限、有効期限などの情報です。標準クレーム(iss, sub, expなど)とカスタムクレームがあります。
{
"sub": "user123",
"name": "Taro Yamada",
"role": "ADMIN",
"exp": 1714828800
}
Payload部分は暗号化されていないため、第三者でもBase64デコードすれば閲覧できます。よって、機密情報は入れないよう注意が必要です。
Signature(署名)
Signatureは、HeaderとPayloadを結合し、秘密鍵で署名を行った文字列です。これにより、トークンの改ざんを検知できます。署名にはHeaderで指定したアルゴリズムが使われます。
public static String createSignature(String header, String payload, String secret) {
String data = header + "." + payload;
return Base64.getUrlEncoder().withoutPadding()
.encodeToString(hmacSha256(data, secret));
}
この署名が一致すれば、トークンが正しい発行者によって作られたことが確認できます。
SpringアプリケーションでのJWT構造利用例
Spring Bootと@Controllerを使った環境では、ログイン時にユーザー名などをPayloadに含めたJWTを生成することができます。
@Controller
public class TokenController {
@PostMapping("/generate-token")
public ResponseEntity<String> generate(@RequestParam String username) {
String token = JwtUtil.generateToken(username);
return ResponseEntity.ok(token);
}
}
このように、JWTの3つの構造を正しく理解して生成・検証することは、安全な認証・認可システムを作るための基礎となります。
3. JWTのHeader(トークンタイプとアルゴリズム)
JWT(JSON Web Token)の先頭部分であるHeaderには、トークンの種類と署名に使用するアルゴリズムが記載されています。この情報はトークンの性質や検証方法をサーバーが正しく理解するために必要不可欠です。
Headerは一般的に非常にシンプルで、次のようなJSON形式になります。
{
"alg": "HS256",
"typ": "JWT"
}
ここで「typ」はトークンのタイプを示し、多くの場合は「JWT」となります。「alg」は署名のアルゴリズムで、HS256(HMAC SHA-256)やRS256(RSA SHA-256)などが使われます。
このJSONをBase64URL形式でエンコードすると、JWTの最初の部分になります。
SpringアプリケーションでこのHeaderを作る場合、Javaコードでは次のように記述できます。
Map<String, Object> header = new HashMap<>();
header.put("alg", "HS256");
header.put("typ", "JWT");
String headerJson = new ObjectMapper().writeValueAsString(header);
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
この処理によって作られたencodedHeaderがJWTの先頭部分として利用されます。
4. JWTのPayload(クレームとその役割)
PayloadはJWTの中心部分で、実際のデータやユーザー情報が格納されます。この情報は「クレーム(claims)」と呼ばれ、標準クレームとカスタムクレームの2種類があります。
代表的な標準クレームには以下があります。
- iss(Issuer): トークンの発行者
- sub(Subject): トークンの対象(ユーザー識別子など)
- exp(Expiration): トークンの有効期限
- iat(Issued At): トークンの発行日時
例として、以下のようなPayloadを考えます。
{
"sub": "user001",
"name": "Hanako Yamada",
"role": "USER",
"exp": 1714828800
}
このJSONをBase64URLでエンコードすると、JWTの2つ目の部分になります。
SpringでこのPayloadを生成する場合、次のようなコードを使います。
Map<String, Object> claims = new HashMap<>();
claims.put("sub", "user001");
claims.put("name", "Hanako Yamada");
claims.put("role", "USER");
claims.put("exp", System.currentTimeMillis() / 1000 + 3600); // 1時間後
String payloadJson = new ObjectMapper().writeValueAsString(claims);
String encodedPayload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
Payloadには暗号化が施されていないため、誰でもデコードできます。機密性の高い情報は含めないのが基本です。
5. JWTのSignature(署名の仕組みと役割)
JWTの最後の部分であるSignatureは、トークンの完全性と正当性を保証するための署名です。HeaderとPayloadをドットで連結し、それを秘密鍵または公開鍵暗号方式で署名して生成します。
署名の生成式は以下の通りです。
Signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
例えば、HS256アルゴリズムを使う場合のJavaコードは以下のようになります。
private static String hmacSha256(String data, String secret) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256_HMAC.init(secret_key);
return Base64.getUrlEncoder().withoutPadding().encodeToString(sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8)));
}
public static String createSignature(String encodedHeader, String encodedPayload, String secret) throws Exception {
String data = encodedHeader + "." + encodedPayload;
return hmacSha256(data, secret);
}
この署名部分を含めることで、トークンが改ざんされていないか検証できるようになります。もしHeaderやPayloadの内容が改ざんされていれば、署名の検証に失敗します。
署名の検証の流れ
JWTを受け取ったサーバーは、同じアルゴリズムと秘密鍵で再度署名を作成し、トークンに含まれる署名と一致するかを比較します。一致すればトークンは有効、そうでなければ無効と判断します。このプロセスにより、第三者による不正な改ざんを防ぐことができます。
6. JWT構造を理解するメリット(デバッグ・セキュリティ対策など)
JWT(JSON Web Token)の構造を理解しておくと、開発や運用のさまざまな場面で大きなメリットがあります。まず、トークンの内容をBase64デコードすれば、ヘッダーやペイロードの情報を簡単に確認できます。これにより、認証や認可のトラブルが発生した際に、原因を素早く特定することが可能です。
例えば、ユーザーが特定のAPIにアクセスできない場合、JWTのペイロードに含まれるロール情報や有効期限(exp)を確認することで、権限不足や期限切れが原因かどうか判断できます。
また、構造を理解しているとセキュリティ面での対策が強化されます。ペイロード部分が暗号化されていないことを知っていれば、個人情報や機密データを直接入れないよう設計段階で注意できますし、署名部分が改ざん防止に使われる仕組みを把握していれば、必ず検証を行う実装を忘れずに済みます。
7. Spring SecurityでJWT構造を活用する方法
Spring SecurityとJWTを組み合わせることで、セッションレスかつ高パフォーマンスな認証・認可機能を実装できます。特に、JWTのヘッダーやペイロード構造を理解しておくと、フィルター処理や認可ロジックをより柔軟に設計できます。
以下は、JWTをHTTPリクエストのAuthorizationヘッダーから取得し、署名検証とペイロード解析を行うフィルターの一例です。
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (JwtUtil.validateToken(token)) {
String username = JwtUtil.getUsernameFromToken(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, List.of());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
このように、JWTの構造を知っていれば、ヘッダー部分からアルゴリズムを確認し、ペイロードからユーザー名や権限を抽出する処理を実装できます。結果として、アプリケーション全体で統一的な認証基盤を構築できます。
8. JWT構造を安全に扱うための注意点(秘密鍵管理、情報の最小化など)
JWTは便利な技術ですが、その構造を誤って扱うとセキュリティリスクが高まります。以下は安全に利用するための主な注意点です。
秘密鍵の安全な管理
署名に使う秘密鍵は、必ず安全な場所に保管します。ソースコードに直接書き込むのではなく、環境変数や外部設定ファイル(プロパティファイル)に置き、アクセス制限をかけます。
// application.properties から秘密鍵を読み込む例
@Value("${jwt.secret}")
private String secretKey;
ペイロード情報の最小化
JWTのペイロードは暗号化されていないため、誰でもBase64デコードして閲覧できます。そのため、含める情報は必要最低限にし、機密性の高いデータは入れないようにします。
有効期限の設定
長期間有効なトークンは漏えい時のリスクが高いため、短めの有効期限(例えば15分〜1時間程度)を設定します。必要に応じてリフレッシュトークンを併用します。
署名検証の徹底
受け取ったJWTは必ず署名を検証し、改ざんされていないことを確認します。検証を省略すると、攻撃者が自由にペイロードを書き換えられる危険があります。
アルゴリズムの固定化
Headerのalgパラメータで署名アルゴリズムを指定しますが、受信時にも想定外のアルゴリズムが指定されていないかを確認する必要があります。特に「none」アルゴリズムは危険なので受け入れないようにします。
安全なJWT利用のまとめ
JWTは構造を理解して正しく扱えば、セッションレスでスケーラブルな認証を実現できます。しかし、秘密鍵管理やペイロード設計を誤ると、深刻なセキュリティリスクを招きます。Spring Securityと組み合わせて使う場合も、署名検証・期限チェック・権限管理を徹底し、安全な認証・認可システムを構築しましょう。