JWTとは?初心者でもわかるJSON Web Tokenの基本概念
新人
「先輩、JWTっていう言葉をよく聞くんですが、いったい何なんですか?」
先輩
「JWTはJSON Web Tokenの略で、WebアプリケーションやAPIでよく使われる認証・認可のためのトークン形式だよ。」
新人
「認証と認可って、どういうときに使い分けるんですか?」
先輩
「認証はユーザーが誰かを確認することで、認可はそのユーザーが何をできるかを制御することなんだ。JWTはこの両方に役立つんだよ。」
新人
「なるほど!じゃあ、具体的にどんな場面で使われるんですか?」
先輩
「例えば、ログイン後にサーバーがユーザー情報を保持せず、トークンを返してクライアント側でやり取りするような場面だね。」
1. JWT(JSON Web Token)とは?
JWT(JSON Web Token)は、ユーザーの認証情報やその他のデータを安全にコンパクトに表現するための文字列形式です。JSON形式のデータをBase64でエンコードし、署名を付与して改ざんを防ぎます。認証や認可の仕組みで広く利用されており、特にシングルページアプリケーション(SPA)やモバイルアプリ、REST APIとの通信において活躍します。
JWTは3つの部分から構成されており、「ヘッダー」「ペイロード」「署名」がドットで区切られています。これらをBase64でエンコードすることでURLでも安全に送受信できます。
JWTはセッションレスな認証に向いており、サーバー側に状態を保持せず、スケーラブルな認証処理を可能にします。
JWTが必要とされる背景
従来のWebアプリケーションはサーバーにセッション情報を保存して認証状態を管理していました。しかし、複数のサーバーでスケールアウトする場合や、モバイル・フロントエンドが多様化する現代の開発環境では、サーバーに依存しない認証方式が求められます。JWTはこの要件に応える技術として注目されています。
2. JWTがどのように認証・認可で利用されるのか
JWTは主に認証と認可の両方で利用されます。ログイン時にユーザーが正しい資格情報を入力すると、サーバーはJWTを生成しクライアントに返します。その後、クライアントはAPIや別のリソースにアクセスするたびに、このJWTをHTTPヘッダーに付けて送信します。
サーバーは受け取ったJWTの署名を検証し、改ざんがないことを確認します。これにより、追加のセッション情報を保持しなくても、ユーザーの認証状態を判断できます。また、JWTのペイロード部分に含まれるロールや権限情報を使って、特定のリソースへのアクセス制御(認可)も行えます。
SpringアプリケーションでのJWT利用例
Spring Securityと組み合わせてJWTを使えば、セッションを使わずに安全なログイン機能を構築できます。以下は、@Controllerを用いてログイン処理を行い、JWTを発行するイメージのサンプルコードです。
@Controller
public class LoginController {
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username, @RequestParam String password) {
// 認証処理(省略)
String token = JwtUtil.generateToken(username);
return ResponseEntity.ok(token);
}
}
JWTを使ったリクエストの流れ
- ユーザーがログイン情報を送信
- サーバーがJWTを生成し返却
- クライアントがJWTをローカルに保存
- APIリクエスト時にJWTをAuthorizationヘッダーに付与
- サーバーがJWTを検証し、認証・認可を実行
3. JWTの構造(ヘッダー・ペイロード・署名)
JWT(JSON Web Token)は、ヘッダー・ペイロード・署名という三つの部分で構成されます。それぞれの部分はBase64URL形式でエンコードされ、ドットで区切られて一つの文字列になります。この構造を理解することは、JWTを安全に活用するための第一歩です。
ヘッダー
ヘッダーにはトークンの種類(typ)と署名に使用するアルゴリズム(alg)が記載されます。例えば、署名アルゴリズムがHMAC SHA256の場合、ヘッダーは次のようになります。
{
"alg": "HS256",
"typ": "JWT"
}
ペイロード
ペイロードにはユーザー情報や権限情報などが含まれます。これらはクレーム(claims)と呼ばれ、標準クレーム(iss, exp, subなど)とカスタムクレームの両方が使えます。ペイロードの例は以下の通りです。
{
"sub": "user123",
"name": "Taro Yamada",
"role": "ADMIN",
"exp": 1714828800
}
署名
署名は、ヘッダーとペイロードを結合して秘密鍵で署名した文字列です。これにより、トークンが改ざんされていないことをサーバー側で確認できます。署名の生成には、ヘッダーとペイロードをBase64URLでエンコードし、ドットで結合した文字列をアルゴリズムに基づいて暗号化します。
4. JWTの作成と署名の仕組み
JWTを作成するには、まずヘッダーとペイロードのJSONデータを用意し、それぞれをBase64URL形式でエンコードします。その後、エンコードされた文字列をドットで結合し、指定されたアルゴリズムと秘密鍵を用いて署名を生成します。この署名がJWTの第三部分になります。
Spring BootやSpring SecurityのプロジェクトでJWTを作成する場合、@ControllerクラスからJWTユーティリティクラスを呼び出して生成する方法が一般的です。
public class JwtUtil {
private static final String SECRET_KEY = "mysecretkey";
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
この例では、ユーザー名をsetSubjectで設定し、有効期限を1時間後に指定しています。最後にsignWithメソッドでHS256アルゴリズムと秘密鍵を使って署名を行い、JWT文字列を生成します。
JWT作成の具体的な流れ
- ヘッダーJSONを作成し、Base64URLでエンコード
- ペイロードJSONを作成し、Base64URLでエンコード
- ヘッダーとペイロードをドットで連結
- 秘密鍵とアルゴリズムを使って署名を生成
- ヘッダー.ペイロード.署名の形式でJWTを完成
5. JWTの検証方法
JWTを受け取ったサーバーは、そのトークンが改ざんされていないか、そして有効期限が切れていないかを検証します。検証には、トークンの署名部分を秘密鍵で確認する方法が使われます。これにより、発行元が正しいかを判断できます。
Spring Securityを使った場合、フィルタークラスでHTTPヘッダーのAuthorizationに含まれるJWTを取得し、検証を行う実装が一般的です。
public static boolean validateToken(String token) {
try {
Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token);
return true;
} catch (JwtException e) {
return false;
}
}
このコードでは、parseClaimsJwsメソッドでJWTを解析し、署名が有効かどうかを確認しています。不正なトークンや期限切れのトークンであれば例外が発生します。
検証時の注意点
- 有効期限(exp)を必ず確認すること
- 署名のアルゴリズムと秘密鍵が一致しているかを確認
- 必要に応じて発行者(iss)や対象者(sub)も検証
これらの検証を適切に行うことで、JWTを使った認証・認可の安全性が高まります。JWTは便利な技術ですが、適切な検証を怠るとセキュリティリスクが増すため注意が必要です。
6. JWTを使うメリット(セッションレス、スケーラビリティなど)
JWT(JSON Web Token)を利用する最大のメリットの一つは、セッションレス認証を実現できることです。従来のセッション方式では、サーバー側にユーザーごとのセッション情報を保存し、アクセス時に参照する必要がありました。しかし、JWTでは認証に必要な情報がトークン自体に含まれているため、サーバーに状態を持たせる必要がありません。
この特徴により、スケーラビリティの向上が可能になります。複数のサーバーを使う負荷分散構成でも、各サーバーが共通のセッションストレージを参照する必要がなく、JWTを受け取って検証するだけで認証が成立します。
さらに、JWTは標準仕様であるため、JavaやSpring以外のさまざまな言語やフレームワークとも互換性があります。例えば、バックエンドがJava、フロントエンドがJavaScriptやモバイルアプリでも、共通のJWTを使って認証・認可が行えます。
セッションレスの具体例
Springアプリケーションでセッションレスを実現する場合、ログイン時にJWTを発行し、以降はHTTPリクエストにJWTを含めることで認証を行います。サーバーはトークンの署名と有効期限を検証するだけで、ユーザーの認証状態を判断できます。
7. Spring SecurityとJWTの連携方法
Spring SecurityとJWTを組み合わせることで、安全かつスケーラブルな認証基盤を構築できます。基本的な流れは次の通りです。
- ログインリクエストを受け取る
- ユーザー情報を認証する
- 認証成功時にJWTを生成しクライアントへ返す
- クライアントは以後のリクエストにJWTを付与する
- サーバー側のフィルターがJWTを検証し、認証情報を設定する
以下は、JWTを生成しSpring Securityの認証処理に組み込む例です。
@Controller
public class AuthController {
@PostMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestParam String username, @RequestParam String password) {
// ユーザー認証処理(省略)
String token = JwtUtil.generateToken(username);
return ResponseEntity.ok(token);
}
}
フィルターによるJWT検証
Spring Securityでは、JWT検証用のフィルタークラスを作成して、全てのリクエスト前に実行させます。このフィルターはHTTPヘッダーのAuthorizationからトークンを取得し、JwtUtilクラスで検証します。
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
if (JwtUtil.validateToken(token)) {
// 認証情報をセキュリティコンテキストに設定(省略)
}
}
chain.doFilter(request, response);
}
}
8. JWTを安全に利用するための注意点(有効期限、秘密鍵管理など)
JWTは便利で効率的な技術ですが、安全に利用するためにはいくつかの重要なポイントがあります。これらを無視すると、不正アクセスや情報漏えいのリスクが高まります。
有効期限の設定
JWTには必ず有効期限(exp)を設定しましょう。無期限のトークンは、万一漏えいした場合に長期間悪用される恐れがあります。短めの有効期限を設定し、必要に応じてリフレッシュトークンを使う設計が推奨されます。
秘密鍵の厳重管理
署名に使う秘密鍵は、外部に漏れないように環境変数や安全な設定ファイルに保存します。ソースコードに直接書き込むのは避けましょう。
HTTPSの利用
JWTは通信経路上で盗聴されないよう、必ずHTTPSを使用してください。平文のHTTPで送信すると、トークンが漏えいする危険があります。
不要な情報を含めない
ペイロードに含める情報は最小限にし、パスワードやクレジットカード番号などの機密情報は絶対に含めないようにしましょう。JWTはBase64でエンコードされていますが、暗号化ではないため誰でもデコードできます。
安全なJWT運用のまとめポイント
- 有効期限は短く設定し、リフレッシュトークンを併用する
- 秘密鍵は安全に保管し、定期的に更新する
- HTTPS通信を必ず使用する
- ペイロードには機密情報を含めない
- 不要になったトークンは即座に無効化する
これらの運用ルールを守ることで、JWTを使った認証システムの安全性を高めることができます。Spring Securityと組み合わせれば、Java・Springの開発環境でも高いセキュリティを保ちながら効率的な認証・認可が可能になります。
まとめ
ここまで、JWT(JSON Web Token)の基本概念から、その構造、Spring Securityを用いた具体的な実装方法、そして運用上の注意点について詳しく解説してきました。現代のWeb開発において、JWTはもはや欠かせない技術スタックの一つと言っても過言ではありません。特に、マイクロサービスアーキテクチャやシングルページアプリケーション(SPA)、モバイルアプリとの通信において、その真価を発揮します。
JWTを導入する最大の意義は、サーバーの負荷を軽減し、柔軟なスケーラビリティを確保できる点にあります。従来のセッション管理では、ユーザー数が増えるたびにサーバーのメモリ消費が激しくなり、複数のサーバー間で情報を共有するための複雑な仕組み(セッションレプリケーションやRedisなどの外部ストレージ)が必要でした。しかし、JWTであればトークン自体が認証情報を持っているため、どのサーバーにリクエストが飛んでも、共通の秘密鍵さえあれば即座に認証を完了させることができます。
JWTの核心を再確認
JWTを正しく扱うために、以下の3つの要素は必ず覚えておきましょう。
- ステートレス(セッションレス): サーバー側に状態を保存しないため、インフラの拡張が容易です。
- コンパクトでポータブル: Base64URLエンコードされているため、URLやHTTPヘッダー、POSTパラメータとして簡単に受け渡しが可能です。
- 高い信頼性: 署名(Signature)によってデータの改ざんを検知できるため、クライアントから送られてきた情報の正当性を担保できます。
実践的なサンプルコード:JWTの生成とパース
実際の現場でよく使われる、Javaライブラリ(jjwtなど)を想定した詳細なサンプルコードを振り返ります。コントローラー層やサービス層でどのように値をセットし、有効期限を管理すべきかを確認しましょう。
@Service
public class TokenService {
// 秘密鍵は本来、外部設定ファイルから取得する
private static final String SECRET_KEY = "your-high-security-secret-key-that-is-very-long";
private static final long EXPIRATION_TIME = 86400000; // 24時間
/**
* ユーザー情報を元にJWTを生成する
*/
public String generateFullToken(String username, List<String> roles) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", roles);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
/**
* トークンからクレームを抽出する
*/
public Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
セキュリティを一段高めるための運用ガイド
JWTを導入して「動いたから完成」ではありません。実際の商用環境では、セキュリティホールを作らないための「攻め」と「守り」の設計が重要です。
1. ペイロード情報の最小化
「JWTは暗号化ではない」という点を改めて強調します。Base64デコードを行えば、中身のJSONは誰でも閲覧可能です。したがって、ユーザーのパスワードや個人情報、社外秘の情報などは絶対に含めないでください。あくまで「誰であるか」「どんな権限があるか」という識別子にとどめるのが鉄則です。
2. アルゴリズムの固定
「Noneアルゴリズム攻撃」という手法が存在します。これはJWTのヘッダーで署名アルゴリズムを none に書き換えることで、署名検証をスキップさせようとする攻撃です。ライブラリを使用する際は、常に特定のアルゴリズム(HS256やRS256など)のみを許可する設定を徹底しましょう。
3. リフレッシュトークンの戦略
アクセストークンの有効期限を数十分~1時間程度と短く設定し、再発行用の「リフレッシュトークン」を別途用意する設計が一般的です。リフレッシュトークンはより厳重に保管(データベース保存やHttpOnlyクッキーなど)することで、万が一アクセストークンが奪われても、被害を最小限に抑えることができます。
まとめ:これからの認証スタンダード
クラウドネイティブな開発が進む中で、JWTのようなオープン標準に基づいた認証方式は今後も主流であり続けるでしょう。Spring Securityという強力なフレームワークを活用すれば、複雑な処理も簡潔に記述できます。しかし、その裏側にある「なぜ安全なのか」「何に注意すべきか」という本質的な仕組みを理解しておくことで、トラブルに強い、堅牢なシステムを構築できるようになります。
ぜひ、今回紹介したコードや概念を自身のプロジェクトに当てはめて、より安全で快適なユーザー体験を提供できるWebアプリケーションを目指してください。
生徒
「先生、ありがとうございました!JWTの構成要素である『ヘッダー・ペイロード・署名』が、ドットでつながった一つの文字列になっていることがよく分かりました。思っていたよりシンプルですね。」
先生
「その通り。シンプルだからこそ、URLやHTTPヘッダーに乗せてどこへでも持ち運べるのがJWTの強みなんだ。でも、そのシンプルさに潜む『注意点』は理解できたかな?」
生徒
「はい!特に『Base64でエンコードされているだけで、暗号化されているわけではない』という話が衝撃でした。デコードサイトを使えば誰でも中身を見られちゃうから、秘密のデータは入れてはいけないんですよね。」
先生
「その通り!中身が見えるからこそ、改ざんを防ぐための『署名』が重要になるんだ。サーバーが自分の持っている秘密鍵で署名を照合することで、クライアントが勝手にペイロードを書き換えていないかチェックできる。これがJWTの信頼の根源だよ。」
生徒
「なるほど。あと、Spring Securityのフィルターで Authorization ヘッダーから Bearer を取り除く処理を書きましたが、あのあたりが実際の認証の要になるんですね。」
先生
「いいところに気づいたね。リクエストが来るたびにそのフィルターが動いて、トークンの有効期限が切れていないか、署名が正しいかを確認する。これがセッションレス認証の基本サイクルだよ。これがマスターできれば、どんなモダンなAPI開発でも通用するよ。」
生徒
「ありがとうございます!まずは短い有効期限の設定と、秘密鍵を環境変数に逃がすところから実装を直してみます。もっと深く学びたくなりました!」