🏂はじめに
こんにちは!
年末も近くなり、寒さが身に染みる季節となってきましたね!
寒さが増してくると趣味のスノーボードとAdvent Calenderが思い浮かんできます笑
Qiitaへの投稿は前回のAdvent Calenderへの投稿に続き、2回目となります!
ありがたいことにJavaの経験が今年でようやく3年となり、まだまだ日々学ぶことが多い毎日です。
今回の投稿もそうした日々の現場で学んだ内容の備忘録と、誰かの開発の助けとなれてくれていたらめちゃくちゃ嬉しいです!
今回は現在参画している現場でも使用しているJWTについて、改めて学習してみようと思います![]()
1. JWTとは何か
JWT (JSON Web Token) は、認証・認可の情報を安全にやり取りするためのトークン形式です。
トークンのフォーマットは xxxxx.yyyyy.zzzzz のように「ドット区切りで3つの文字列」であり、中身は JSON(ヘッダ + ペイロード)と、その内容を改ざんされていないことを保証するための署名といった構成になっています。
- ①Header: 使用する署名アルゴリズムなどのメタ情報
- ②Payload: ユーザIDや有効期限などのクレーム(claim)
- ③Signature: Header+Payloadを秘密鍵などで署名したもの
例)遊園地の「1日フリーパス」
Header:ヘッダー
→ チケットの種類や形式が書かれた「券の上の説明書き」
「1日フリーパス」「QRコード付きチケット」など
②Payload:ペイロード
→ チケットの中央の“本体情報”
「山田太郎/2025年4月1日有効/全アトラクションOK」など
③Signature:署名
→ 偽造防止のホログラム・スタンプ・ICチップ
「これは本物の遊園地が発行したチケットで、中身も書き換> えられていませんよ」という証拠
以下は、典型的な構成は以下の3部で、「ヘッダ.ペイロード.署名」というフォーマットです。
①Header . ②Payload .③Signature
↓ 記載例
①Header: {"alg":"HS256","typ":"JWT"} .
②Payload: {"sub":"user123","exp":1710000000,"role":"admin"} .
③Signature: HMACSHA256(ヘッダ + "." + ペイロード, 秘密鍵)
↓ JWTトークンに変換
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoiMTcxMDAwMDAwMCIsInJvbGUiOiJhZG1pbiJ9.S4XcN_Je9V58m4IGxhHFq9mTBijCpiMJWFbCwmeK-WQ
※プログラム上ではなく下記のようなWebサイトからでも、Header・Payload・Signature部に任意の項目を設定することでJWTトークン(下記キャプチャに倣うとJSON WEB TOKEN)を発行出来ます。

上記のキャプチャでも利用しているjwt.ioはJWTトークンの作成や検証を行うのに、シンプルで使いやすくオススメです!
・JSON Web Tokens - jwt.io
Javaでは、このトークンを生成・検証するために専用ライブラリを使うのが一般的です。今回はシンプルで扱いやすいAuth0の「java-jwt」を利用します。
ちなみに、ほかには下記のような選択肢があるので、用途によってお選びください!
・まず手軽にサンプルを書きたい
→ Auth0 Java JWT または JJWT
・OAuth2 / OpenID Connect サーバやIDプロバイダと本格連携したい
→ Nimbus JOSE + **JWT(+Spring Security)**を検討
・暗号化(JWE)やJWK管理まで広く扱いたい
→ Nimbus または jose4j
2. ライブラリの導入
2-1. Mavenの場合
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version> <!-- 例:最新版に合わせる -->
</dependency>
2-2. Gradleの場合
implementation 'com.auth0:java-jwt:4.4.0'
※バージョンは必要に応じて更新してください。
3. サンプルコード全体像
まずは、HMAC方式の秘密鍵(key)を使ってJWTトークンを検証するシンプルな例を示します。
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.auth0.jwt.exceptions.JWTVerificationException;
public class JwtVerifySample {
public static void main(String[] args) {
// 実際には環境変数や設定ファイルから取得することを推奨
String key = "your-256-bit-secret";
String token = "クライアントから渡されたJWTトークン文字列";
try {
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier verifier = JWT
.require(algorithm)
// .withIssuer("example-issuer") // 必要に応じて条件を追加
.build();
DecodedJWT jwt = verifier.verify(token);
// 検証成功時の処理
String subject = jwt.getSubject();
String issuer = jwt.getIssuer();
String userId = jwt.getClaim("userId").asString();
System.out.println("JWT verification succeeded");
System.out.println("sub: " + subject);
System.out.println("iss: " + issuer);
System.out.println("userId: " + userId);
} catch (JWTVerificationException e) {
// 検証失敗時の処理(署名不正、有効期限切れなど)
System.err.println("JWT verification failed: " + e.getMessage());
}
}
}
この中の以下3行が、JWT検証の要となる部分です。
Algorithm algorithm = Algorithm.HMAC256(key);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
ここからは、この3行を分解して解説します。
4. Algorithm.HMAC256(key) で何をしているか
Algorithm algorithm = Algorithm.HMAC256(key);
4-1. Algorithmクラスの役割
Algorithm クラスは、JWTの署名・検証に使うアルゴリズムと秘密情報(鍵)をまとめたものです。
HMAC256 = HMAC-SHA256 署名方式
引数の key は共有秘密鍵(サーバ側で保持する文字列)
4-2. HMAC方式の特徴
「秘密鍵を共有」する対称鍵方式
トークン生成側と検証側で同じ秘密鍵を利用
秘密鍵が漏れると署名偽造が可能になるため、管理が非常に重要
本番環境では、以下のような管理を推奨します。
- キーは環境変数やシークレットマネージャから取得する
- ソースコードに直書きしない
- ログに出力しない
String key = System.getenv("JWT_SECRET");
// or: 設定ファイルやVaultなどから取得
Algorithm algorithm = Algorithm.HMAC256(key);
※今回は割愛していますが、公開鍵使用時はAlgorithmオブジェクト作成にあたって署名アルゴリズムの設定部分を>下記のように変更することで、公開鍵による検証を実施できます。
// 公開鍵(例:RS256)の場合 RSAPublicKey publicKey = ...; // PEMなどから読み込み Algorithm algorithm = Algorithm.RSA256(publicKey, null);
5. JWT.require(algorithm).build() で何をしているか
JWTVerifier verifier = JWT.require(algorithm).build();
5-1. JWTVerifierとは
JWTVerifier は、トークンの検証ロジックを持つオブジェクトです。
このオブジェクトに対して verify(token) を呼び出すと、以下をまとめてチェックできます。
- 署名の正当性
- 有効期限(exp)のチェック
- not-before(nbf)のチェック
- 任意のクレーム(issuer, audienceなど)の一致チェック
5-2. 検証条件の追加
JWT.require(algorithm) の後にメソッドチェーンで条件を追加できます。
JWTVerifier verifier = JWT
.require(algorithm)
.withIssuer("your-issuer") // iss クレームをチェック
.withAudience("your-audience") // aud クレームをチェック
// .acceptLeeway(1) // 時刻誤差の許容(秒)
.build();
例:発行者(issuer)が自サービスのものか確認したいケース
JWTVerifier verifier = JWT
.require(algorithm)
.withIssuer("my-service")
.build();
このように、JWTVerifier は「どのような条件でトークンを正しいとみなすか」を定義する役割を持ちます。
6. verifier.verify(token) で何をしているか
DecodedJWT jwt = verifier.verify(token);
6-1. verifyの動作
verifier.verify(token) を呼ぶと、主に次のことが行われます。
-
トークン文字列をパースして、header・payload・signature を切り出す
-
header・payload からアルゴリズム情報やクレームを読み込む
-
渡された Algorithm(ここでは HMAC256)で署名を再計算し、signature と同一か確認
-
exp や nbf などの標準クレーム、有効期間をチェック
-
withIssuer などで指定した条件に合致するかチェック
いずれかが満たされない場合は、JWTVerificationException がスローされます。
6-2. DecodedJWTからクレームを取り出す
検証が成功すると、DecodedJWT オブジェクトが返ってきます。ここから各種クレームを取得できます。
DecodedJWT jwt = verifier.verify(token);
// 標準クレーム
String issuer = jwt.getIssuer();
String subject = jwt.getSubject();
Date issuedAt = jwt.getIssuedAt();
Date expires = jwt.getExpiresAt();
// カスタムクレーム
String userId = jwt.getClaim("userId").asString();
List<String> roles = jwt.getClaim("roles").asList(String.class);
このように、「改ざんされていないか」「信頼できる発行者が作成したものか」「有効期限内か」等を自動的に検証する役割を持ちます。
7. よくある落とし穴・注意点
7-1. トークンの有効期限(exp)を必ず入れる
JWTは基本的に「ステートレス」なトークンであり、サーバ側でトークンの無効化状態を管理しない運用もできますが、その分「短い有効期限」が重要です。
トークン生成時には exp を必ず設定しましょう。
String token = JWT.create()
.withIssuer("my-service")
.withSubject("user-123")
.withClaim("userId", "user-123")
.withExpiresAt(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分
.sign(algorithm);
7-2. 平文の「暗号」と勘違いしない
JWTのpayload部分は Base64URL エンコードされているだけで、暗号化されているわけではありません。
トークンを手に入れた人なら誰でもpayload部分を簡単に読めます。
機密情報(パスワード、クレジットカード番号等)や、個人情報は入れない
※個人情報を含む場合には必要最低限の情報のみを含め、氏名や住所などの機密性の高い情報は含めない
別途payload部分の暗号化やAPI側のアクセス制御等の対策を行う
7-3. HTTPS必須
JWTトークンはHTTPヘッダ(例:Authorizationヘッダ)で送信されることが多いですが、HTTPSを使わないとトークンがそのまま盗聴されます。
本番環境では必ず HTTPS を利用
トークンをクエリパラメータに乗せるのは避ける(ログに残りやすいため)
8. まとめ
お忙しい中で最後までお読み頂きまして、ありがとうございます!!
今回の執筆を通して、現場では周辺のソースを確認しながらその都度やり過ごしていたこともあったため、改めて体系的に学習することが出来て良かったと思いました。
今回学んだ内容で目から鱗だった部分もあり、出来る限り品質向上のためにも大事だと感じた観点は業務にも取り入れていきたいです。
参考記事・WEBサイト
・【2025年最新】JavaでのJWT認証 - 非推奨APIの変更点と安全な実装方法
https://qiita.com/harukii1999/items/3d1d969112a5d481eec6
・Java JWTとは?詳細情報を解説
https://openstandia.jp/oss_info/java_jwt/
・JSON Web Tokens - jwt.io
https://www.jwt.io/ja