Nimbus JOSE + JWT

JavaJWTJOSE認証ライブラリ暗号化署名JSON Web Tokenセキュリティ

認証ライブラリ

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仕様の変更に伴うバージョン管理の複雑さ

主要リンク

書き方の例

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());
    }
}