JWTの発行と検証の流れを初心者向けにやさしく解説!Spring環境で学ぶトークン管理の基本
新人
「先輩、JWTっていう単語をよく見かけるんですが、これは何なんですか?」
先輩
「JWTはJSON Web Tokenの略で、システム間やクライアントとサーバー間で安全に情報をやり取りするための仕組みだよ。」
新人
「安全にやり取りって具体的にどういうことですか?」
先輩
「簡単に言うと、サーバーが発行する署名付きの情報パッケージをクライアントが保持して、それを使ってログイン状態や権限を確認できる仕組みなんだ。」
新人
「なるほど!じゃあまずは基本から教えてください!」
1. JWTとは何か
JWT(JSON Web Token)は、JSON形式のデータを安全にやり取りするためのトークンフォーマットです。特にWebアプリケーションやAPIでの認証や情報の安全な伝達に使われます。JWTの発行と検証の流れを理解すると、ログイン機能やアクセス制御を効率的に実装できるようになります。
JWTは暗号化された署名を含んでおり、改ざんを防ぐことができます。そのため、クライアントとサーバー間で信頼できる情報を送受信できます。Spring Frameworkでも、JWTの発行や検証は簡単に組み込むことが可能です。
2. JWTが使われる背景と目的
従来のWebアプリケーションでは、ユーザーのログイン状態を維持するためにサーバー側でセッション情報を管理していました。しかし、APIやマイクロサービスの普及により、セッション管理だけでは不便になってきました。
そこで登場したのがJWTです。JWTを使えば、ユーザー情報や認証状態をトークンに含めてクライアント側で保持できます。サーバーはそのトークンを検証するだけで認証が可能になるため、ステートレスな認証を実現できます。
例えば、ショッピングサイトやSNSでは、ログイン後に複数のサーバーを経由しても認証情報が維持されます。これはJWTによる発行と検証の仕組みが支えているからです。
3. JWTの基本構造
JWTは3つの部分で構成されています。
- Header(ヘッダー):トークンの種類や署名アルゴリズムの情報
- Payload(ペイロード):ユーザーIDや有効期限などの実際のデータ
- Signature(署名):ヘッダーとペイロードを秘密鍵で署名したもの
それぞれの部分はBase64でエンコードされ、ドット.で区切られた文字列になります。
public class JwtExample {
public static void main(String[] args) {
String header = "{ \"alg\": \"HS256\", \"typ\": \"JWT\" }";
String payload = "{ \"sub\": \"user123\", \"exp\": 1672531199 }";
String signature = "署名部分";
String jwt = header + "." + payload + "." + signature;
System.out.println(jwt);
}
}
{ヘッダー}.{ペイロード}.{署名}
実際には、このJWTをサーバーが発行し、クライアントがHTTPリクエストのヘッダーに付けて送信します。そしてサーバー側で検証を行い、正しいトークンであればアクセスを許可します。
Spring環境でのJWT発行と検証の前提
この記事では、pleiadesを使用してGradleプロジェクトを作成し、@Controllerで処理を行います。依存関係はpleiadesのチェック機能で追加し、Spring以外の不要なライブラリは使いません。このように環境を統一することで、初心者でも迷わずにJWTの発行と検証を学べます。
4. JWTの発行手順(Spring環境での実装例)
JWTの発行は、ユーザーがログインに成功したタイミングで行うのが一般的です。Spring環境では、認証処理を行ったあとにトークンを生成し、そのトークンをクライアントへ返します。トークンにはユーザーの識別情報や有効期限などを含めます。
ここでは、pleiadesで作成したGradleプロジェクトにおいて、@Controllerを使ったシンプルなJWT発行の例を紹介します。依存関係はpleiadesのチェックで追加し、外部ライブラリは最小限にします。
@Controller
public class AuthController {
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username, @RequestParam String password) {
if ("user".equals(username) && "pass".equals(password)) {
String token = JwtUtil.generateToken(username);
return ResponseEntity.ok(token);
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("認証失敗");
}
}
public class JwtUtil {
private static final String SECRET_KEY = "mysecretkey";
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
この例では、ユーザー名とパスワードを受け取り、認証に成功した場合にJWTを発行します。トークンは有効期限付きで生成され、改ざん防止のために署名が付けられます。
5. JWTの署名と秘密鍵の役割
JWTの重要なポイントのひとつが署名部分です。署名は、トークンが改ざんされていないことを保証するために使われます。Spring環境では、この署名を生成するために秘密鍵(または共有鍵)を使用します。
秘密鍵はサーバー側で厳重に管理し、絶対に外部に漏らさないようにします。この鍵を使って署名を作成し、クライアントから送られてきたトークンの検証時にも同じ鍵で署名をチェックします。
public static boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
この検証処理では、署名が一致すればトークンは正しいと判断されます。逆に署名が異なればトークンは無効となり、アクセスは拒否されます。これにより、JWTの検証プロセスは安全性を高めます。
6. JWTをHTTPレスポンスに含めて送信する方法
JWTの発行後、クライアントに返す方法はいくつかありますが、最も一般的なのはHTTPレスポンスのボディに含める方法です。また、Authorizationヘッダーに設定して返すことも多いです。
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username, @RequestParam String password) {
if ("user".equals(username) && "pass".equals(password)) {
String token = JwtUtil.generateToken(username);
return ResponseEntity.ok()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.body("ログイン成功");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("認証失敗");
}
このようにヘッダーに含めて返せば、クライアント側は次回以降のリクエストでこのトークンをそのまま使えます。例えば、APIアクセス時にAuthorizationヘッダーを付けて送信することで、サーバーはトークンを検証してアクセスを許可します。
HTTPレスポンスにJWTを含める際は、HTTPS通信を必ず使うことで盗聴や改ざんを防止できます。これにより、JWTの発行から検証までの流れが安全に実現できます。
補足:SpringでのJWT運用のポイント
JWTを使うと、サーバー側でセッション情報を保持せずに認証が可能になりますが、いくつかの注意点があります。有効期限を短く設定して不正利用を防ぐこと、リフレッシュトークンを活用して安全に再発行すること、そして秘密鍵の管理を徹底することです。これらのポイントを押さえて運用すれば、JWTの発行と検証は非常に強力な認証手段となります。
7. JWTの検証手順(Spring環境での実装例)
JWTの検証は、クライアントから送られてきたトークンが正しいかどうかを確認するための重要な工程です。サーバーは受け取ったトークンの署名や有効期限をチェックし、不正なトークンや期限切れのトークンを拒否します。これにより、認証されたユーザーだけが保護されたリソースへアクセスできるようになります。
Spring環境では、リクエストを受け取るコントローラの処理内やフィルタを使ってこの検証を行います。秘密鍵を使ってトークンを解析し、署名や発行者、有効期限を確認します。
@Controller
public class SecureController {
@GetMapping("/secure-data")
public ResponseEntity<String> getSecureData(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.replace("Bearer ", "");
if (JwtUtil.validateToken(token)) {
return ResponseEntity.ok("保護されたデータにアクセス成功");
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("トークンが無効または期限切れです");
}
}
この例では、HTTPリクエストヘッダーからAuthorizationを取得し、Bearerスキームを取り除いたトークンを検証メソッドに渡しています。検証に成功すればアクセスを許可し、失敗すればエラーレスポンスを返します。
8. JWTの有効期限と更新(トークンのリフレッシュ)
JWTには有効期限が設定されており、この期限を過ぎるとトークンは無効になります。有効期限はexpクレームとしてペイロードに含まれ、サーバーの検証時にチェックされます。
短い有効期限はセキュリティを高めますが、ユーザーは頻繁に再ログインが必要になります。そのため、実際の運用では「リフレッシュトークン」を使ってアクセス用トークンを再発行する仕組みがよく利用されます。
public static String refreshAccessToken(String refreshToken) {
if (validateToken(refreshToken)) {
String username = getUsernameFromToken(refreshToken);
return generateToken(username); // 新しい有効期限で発行
}
throw new RuntimeException("無効なリフレッシュトークン");
}
この例では、リフレッシュトークンの検証に成功した場合に新しいアクセス用JWTを発行しています。こうすることで、ユーザーは長期間ログイン状態を維持できますが、万一トークンが漏洩した場合のリスクもあるため、リフレッシュトークンの保存や送信には特に注意が必要です。
9. JWT利用時のセキュリティ上の注意点
JWTを安全に利用するためには、いくつかの重要なポイントを守る必要があります。以下に代表的な注意点を挙げます。
- HTTPS通信の使用:JWTは暗号化されない部分(ペイロード)を含むため、平文通信では盗聴される可能性があります。常にHTTPSを使用しましょう。
- 短めの有効期限設定:トークンの寿命を短くすることで、万が一盗まれても悪用できる時間を制限できます。
- 署名鍵の厳重管理:秘密鍵は絶対に外部に漏らさないようにし、アクセス制御の厳しい場所に保管します。
- 不要な情報の格納禁止:ペイロードには機密情報を直接含めないようにします。JWTは誰でもデコード可能であるためです。
- トークンの無効化戦略:ログアウト時や権限変更時に特定のトークンを無効化する仕組みを設けます。
特に、JWTは発行後にサーバー側で内容を変更できないため、発行時点で必要な情報だけを入れる設計が重要です。また、長期間の利用や高セキュリティが求められる場合は、署名アルゴリズムの選択や鍵管理の仕組みを慎重に設計する必要があります。
まとめに代えて:安全なJWT運用のために
JWTの発行と検証は、Webアプリケーションの認証とセキュリティにおいて非常に有用ですが、使い方を誤ると脆弱性を生む原因にもなります。今回解説したSpring環境での発行手順、検証方法、有効期限の管理、セキュリティ上の注意点を押さえることで、安全で信頼性の高いトークンベース認証を実装できます。
最終的には、運用環境やアプリケーションの特性に応じて、JWTの発行ポリシーや検証ルールを柔軟に設計し、必要に応じて他のセキュリティ技術と組み合わせて防御力を高めることが重要です。