Nimbus JOSE + JWT
認証ライブラリ
Nimbus JOSE + JWT
概要
Nimbus JOSE + JWTは、Java向けの最も人気で堅牢なJWT(JSON Web Token)ライブラリで、JOSE(JavaScript Object Signing and Encryption)標準を完全実装し、すべての標準署名(JWS)および暗号化(JWE)アルゴリズムをカバーしています。
詳細
Nimbus JOSE + JWTは、Connect2idによって開発されたJava向けの包括的なJWT実装ライブラリです。「Java向けで最も人気で堅牢なJSON Web Token(JWT)ライブラリ」として位置づけられており、JOSE(JavaScript Object Signing and Encryption)標準とJWT(JSON Web Token)標準を完全に実装しています。
このライブラリは、OAuth 2.0アクセストークンやOpenID Connect IDトークンなどの自己完結型トークンの署名と暗号化を担う包括的なAPIを提供しています。Java 7+で動作し、最小限の依存関係で設計されているため、既存のJavaプロジェクトに容易に統合できます。
署名機能では、RS256、RS384、RS512(RSA)、ES256、ES384、ES512(ECDSA)、HS256、HS384、HS512(HMAC)、PS256、PS384、PS512(RSA-PSS)、EdDSA(Ed25519)などの全ての標準JWS署名アルゴリズムをサポートしています。暗号化機能では、RSA-OAEP、AES Key Wrap、ECDH-ES、PBES2などの鍵暗号化アルゴリズムと、AES-GCM、AES-CBC+HMAC-SHA2などのコンテンツ暗号化アルゴリズムをサポートしています。
JSON Web Key(JWK)の完全サポートにより、RSA、EC(楕円曲線)、AES、HMAC鍵の生成、解析、シリアライゼーションが可能です。また、JWK Set操作、鍵ローテーション、鍵の有効期限管理などの高度な機能も提供しています。
Spring Security、OAuth 2.0ライブラリ、マイクロサービスアーキテクチャでの使用実績が豊富で、エンタープライズグレードのセキュリティ要件に対応しています。パフォーマンス最適化により、高負荷環境でも安定した動作を実現し、詳細なエラーハンドリングとロギング機能により、問題の特定と解決を容易にしています。
メリット・デメリット
メリット
- 完全なJOSE/JWT実装: 全ての標準署名・暗号化アルゴリズムをサポート
- 高い安定性: 10年以上の開発実績と豊富な本番環境での使用実績
- 最小依存関係: Java 7+で動作し、外部依存を最小限に抑制
- 包括的なAPI: トークン生成から検証まで完全なライフサイクルをサポート
- JWK完全サポート: JSON Web Keyの生成、管理、ローテーション機能
- エンタープライズ対応: Spring Security、OAuth 2.0との優れた統合
- 高性能: 最適化されたアルゴリズム実装による高速処理
- 詳細ドキュメント: 充実した公式ドキュメントとサンプルコード
デメリット
- Java限定: Java以外の言語では使用不可
- 学習コストが高い: JOSE/JWT仕様の理解が必要
- 設定の複雑さ: セキュリティ設定が複雑になる場合がある
- ライブラリサイズ: 機能が豊富なためライブラリサイズが大きい
- メモリ使用量: 暗号化処理でメモリ消費量が増加する可能性
- デバッグの困難さ: 暗号化エラーのデバッグが困難な場合がある
- バージョン互換性: JOSE仕様の変更に伴うバージョン管理の複雑さ
主要リンク
- Nimbus JOSE + JWT 公式サイト
- GitHub リポジトリ
- JavaDoc API ドキュメント
- Maven Repository
- サンプルコード集
- JOSE & JWT 標準仕様
- Connect2id サポート
書き方の例
Maven依存関係の追加
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>10.3</version>
</dependency>
基本的なJWT作成と検証
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;
import java.util.Date;
public class JWTExample {
public static void main(String[] args) throws Exception {
// HSMAアルゴリズムを使用したJWT作成
createAndVerifyHMACJWT();
// RSAアルゴリズムを使用したJWT作成
createAndVerifyRSAJWT();
}
// HMAC署名を使用したJWT
public static void createAndVerifyHMACJWT() throws Exception {
// 共有秘密鍵
String sharedSecret = "mySecretKey123456789012345678901234567890";
// JWTクレームセットの作成
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("user123")
.issuer("https://example.com")
.audience("https://api.example.com")
.expirationTime(new Date(System.currentTimeMillis() + 60 * 60 * 1000)) // 1時間後
.notBeforeTime(new Date())
.issueTime(new Date())
.jwtID("jwt-id-123")
.claim("role", "admin")
.claim("email", "[email protected]")
.build();
// JWSヘッダーの作成
JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
// SignedJWTオブジェクトの作成
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// HMAC署名の適用
JWSSigner signer = new MACSigner(sharedSecret.getBytes());
signedJWT.sign(signer);
// JWTトークンの取得
String jwtToken = signedJWT.serialize();
System.out.println("JWT Token: " + jwtToken);
// JWT検証
SignedJWT parsedJWT = SignedJWT.parse(jwtToken);
JWSVerifier verifier = new MACVerifier(sharedSecret.getBytes());
if (parsedJWT.verify(verifier)) {
System.out.println("JWT signature verified successfully");
// クレームの取得
JWTClaimsSet claims = parsedJWT.getJWTClaimsSet();
System.out.println("Subject: " + claims.getSubject());
System.out.println("Role: " + claims.getStringClaim("role"));
System.out.println("Email: " + claims.getStringClaim("email"));
// 有効期限チェック
if (claims.getExpirationTime().after(new Date())) {
System.out.println("Token is valid");
} else {
System.out.println("Token has expired");
}
} else {
System.out.println("JWT signature verification failed");
}
}
}
RSA鍵ペアを使用したJWT
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public static void createAndVerifyRSAJWT() throws Exception {
// RSA鍵ペアの生成
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(2048);
KeyPair keyPair = keyGenerator.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// JWTクレームセットの作成
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("user456")
.issuer("https://secure.example.com")
.audience("https://api.example.com")
.expirationTime(new Date(System.currentTimeMillis() + 30 * 60 * 1000)) // 30分後
.issueTime(new Date())
.claim("scope", "read write")
.claim("department", "engineering")
.build();
// JWSヘッダーの作成(RS256アルゴリズム)
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID("key-1")
.type(JOSEObjectType.JWT)
.build();
// SignedJWTオブジェクトの作成
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// RSA署名の適用
JWSSigner signer = new RSASSASigner(privateKey);
signedJWT.sign(signer);
String jwtToken = signedJWT.serialize();
System.out.println("RSA JWT Token: " + jwtToken);
// JWT検証
SignedJWT parsedJWT = SignedJWT.parse(jwtToken);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
if (parsedJWT.verify(verifier)) {
System.out.println("RSA JWT signature verified successfully");
JWTClaimsSet claims = parsedJWT.getJWTClaimsSet();
System.out.println("Subject: " + claims.getSubject());
System.out.println("Scope: " + claims.getStringClaim("scope"));
System.out.println("Department: " + claims.getStringClaim("department"));
}
}
JWEを使用した暗号化JWT
import com.nimbusds.jose.crypto.AESEncrypter;
import com.nimbusds.jose.crypto.AESDecrypter;
public static void createAndDecryptJWE() throws Exception {
// 暗号化鍵(256ビット)
byte[] secretKey = new byte[32]; // 256 bits
new SecureRandom().nextBytes(secretKey);
// JWTクレームセットの作成
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("confidential-user")
.issuer("https://secure.example.com")
.claim("sensitive-data", "top-secret-information")
.claim("security-level", "confidential")
.expirationTime(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分後
.build();
// JWEヘッダーの作成
JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
.contentType("JWT")
.build();
// EncryptedJWTオブジェクトの作成
EncryptedJWT encryptedJWT = new EncryptedJWT(header, claimsSet);
// AES暗号化の適用
JWEEncrypter encrypter = new AESEncrypter(secretKey);
encryptedJWT.encrypt(encrypter);
String jweToken = encryptedJWT.serialize();
System.out.println("JWE Token: " + jweToken);
// JWE復号化
EncryptedJWT parsedJWE = EncryptedJWT.parse(jweToken);
JWEDecrypter decrypter = new AESDecrypter(secretKey);
parsedJWE.decrypt(decrypter);
JWTClaimsSet decryptedClaims = parsedJWE.getJWTClaimsSet();
System.out.println("Decrypted Subject: " + decryptedClaims.getSubject());
System.out.println("Sensitive Data: " + decryptedClaims.getStringClaim("sensitive-data"));
}
JSON Web Key(JWK)の生成と使用
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;
public static void createAndUseJWK() throws Exception {
// RSA JWKの生成
RSAKey rsaKey = new RSAKeyGenerator(2048)
.keyID("rsa-key-1")
.keyUse(KeyUse.SIGNATURE)
.algorithm(JWSAlgorithm.RS256)
.generate();
System.out.println("RSA JWK: " + rsaKey.toJSONString());
// EC JWKの生成
ECKey ecKey = new ECKeyGenerator(Curve.P_256)
.keyID("ec-key-1")
.keyUse(KeyUse.SIGNATURE)
.algorithm(JWSAlgorithm.ES256)
.generate();
System.out.println("EC JWK: " + ecKey.toJSONString());
// JWK Setの作成
JWKSet jwkSet = new JWKSet(Arrays.asList(rsaKey, ecKey));
System.out.println("JWK Set: " + jwkSet.toJSONObject());
// JWKを使用したJWT署名
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("jwk-user")
.issuer("https://jwk.example.com")
.expirationTime(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.build();
JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(rsaKey.getKeyID())
.build();
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
// RSAプライベートキーで署名
JWSSigner signer = new RSASSASigner(rsaKey);
signedJWT.sign(signer);
System.out.println("JWK signed JWT: " + signedJWT.serialize());
// パブリックキーで検証
JWSVerifier verifier = new RSASSAVerifier(rsaKey.toRSAPublicKey());
System.out.println("JWK verification: " + signedJWT.verify(verifier));
}
カスタムクレームとバリデーション
import com.nimbusds.jwt.proc.*;
import com.nimbusds.jose.proc.*;
public static void customClaimsValidation() throws Exception {
String sharedSecret = "mySecretKey123456789012345678901234567890";
// カスタムクレームを含むJWT作成
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject("custom-user")
.issuer("https://custom.example.com")
.audience("https://api.example.com")
.expirationTime(new Date(System.currentTimeMillis() + 60 * 60 * 1000))
.issueTime(new Date())
.claim("role", "premium-user")
.claim("permissions", Arrays.asList("read", "write", "delete"))
.claim("subscription", "premium")
.claim("user-level", 5)
.build();
JWSHeader header = new JWSHeader(JWSAlgorithm.HS256);
SignedJWT signedJWT = new SignedJWT(header, claimsSet);
JWSSigner signer = new MACSigner(sharedSecret.getBytes());
signedJWT.sign(signer);
String token = signedJWT.serialize();
// JWT処理器の設定
ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
// JWS鍵ソースの設定
JWKSource<SecurityContext> keySource = new ImmutableSecret<>(sharedSecret.getBytes());
jwtProcessor.setJWSKeySource(keySource);
// カスタムクレーム検証器の設定
jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
new JWTClaimsSet.Builder()
.issuer("https://custom.example.com")
.audience("https://api.example.com")
.build(),
new HashSet<>(Arrays.asList("sub", "iat", "exp", "role"))
));
try {
// JWT処理と検証
JWTClaimsSet processedClaims = jwtProcessor.process(token, null);
System.out.println("JWT validated successfully");
System.out.println("Role: " + processedClaims.getStringClaim("role"));
System.out.println("Permissions: " + processedClaims.getStringListClaim("permissions"));
System.out.println("User Level: " + processedClaims.getIntegerClaim("user-level"));
// カスタムビジネスロジック検証
String role = processedClaims.getStringClaim("role");
if ("premium-user".equals(role)) {
System.out.println("Premium user access granted");
} else {
System.out.println("Standard user access");
}
} catch (BadJWTException e) {
System.err.println("JWT validation failed: " + e.getMessage());
}
}
JWKセットの読み込みとキーローテーション
import java.net.URL;
public static void jwkSetFromURL() throws Exception {
// JWK SetをURLから読み込み(JWKS endpoint)
URL jwkSetURL = new URL("https://example.com/.well-known/jwks.json");
JWKSet jwkSet = JWKSet.load(jwkSetURL);
System.out.println("Loaded JWK Set with " + jwkSet.getKeys().size() + " keys");
// 特定のキーIDでキーを検索
JWK key = jwkSet.getKeyByKeyId("key-1");
if (key != null) {
System.out.println("Found key: " + key.getKeyID());
// RSA公開キーの取得
if (key instanceof RSAKey) {
RSAKey rsaKey = (RSAKey) key;
RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
// 検証器の作成
JWSVerifier verifier = new RSASSAVerifier(publicKey);
System.out.println("Verifier created for key: " + key.getKeyID());
}
}
// キーローテーション用の複数キー管理
for (JWK jwk : jwkSet.getKeys()) {
System.out.println("Key ID: " + jwk.getKeyID() +
", Algorithm: " + jwk.getAlgorithm() +
", Use: " + jwk.getKeyUse());
}
}
エラーハンドリングとログ
import java.text.ParseException;
public static void errorHandlingExample() {
try {
// 不正なJWTトークンの解析
String invalidToken = "invalid.jwt.token";
SignedJWT.parse(invalidToken);
} catch (ParseException e) {
System.err.println("JWT parsing failed: " + e.getMessage());
}
try {
// 期限切れトークンのシミュレーション
JWTClaimsSet expiredClaims = new JWTClaimsSet.Builder()
.subject("expired-user")
.expirationTime(new Date(System.currentTimeMillis() - 60 * 60 * 1000)) // 1時間前
.build();
// 期限チェック
Date now = new Date();
if (expiredClaims.getExpirationTime().before(now)) {
throw new RuntimeException("Token has expired");
}
} catch (Exception e) {
System.err.println("Token validation error: " + e.getMessage());
}
// JWTプロセッサーでの包括的エラーハンドリング
try {
ConfigurableJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
// 処理設定...
} catch (BadJOSEException e) {
System.err.println("JOSE processing error: " + e.getMessage());
} catch (JOSEException e) {
System.err.println("JOSE operation error: " + e.getMessage());
}
}