Auth0 Java JWT

認証JWTJavaKotlinAuth0トークンセキュリティサーバーサイド

ライブラリ

Auth0 Java JWT

概要

Auth0 Java JWTは、JavaアプリケーションでJSON Web Token(JWT)の作成、検証、デコードを行うためのライブラリです。

詳細

Auth0 Java JWTは、Auth0が開発・提供するJavaおよびKotlin向けのJSON Web Token実装ライブラリです。サーバーサイドJVMアプリケーションでのJWT処理に特化しており、Java LTS版(8、11、17)をサポートしています。

このライブラリは、JWT仕様に準拠したトークンの生成、署名、検証機能を提供し、HMAC、RSA、ECDSAなど複数の暗号化アルゴリズムをサポートしています。署名と検証の両方に対応しており、セキュアな認証システムの構築に必要な機能を包括的に提供します。

主な特徴として、流暢なAPIデザインによる直感的なトークン操作、豊富なクレーム管理機能、例外処理によるエラーハンドリング、Kotlin言語との完全な互換性があります。Auth0のエコシステムとの統合により、企業レベルの認証基盤を簡単に構築できます。

ライブラリはMITライセンスで提供されており、アクティブにメンテナンスされています。Android向けにはJWTDecode.Androidという専用ライブラリが別途提供されており、プラットフォーム固有の最適化が図られています。

メリット・デメリット

メリット

  • 豊富なアルゴリズムサポート: HMAC、RSA、ECDSAなど主要な署名アルゴリズムを包括的にサポート
  • 直感的なAPI: メソッドチェーンによる流暢で読みやすいコード記述
  • Kotlinサポート: Java/Kotlin両言語での完全な互換性
  • セキュリティ重視: 業界標準のセキュリティプラクティスに準拠
  • Auth0統合: Auth0プラットフォームとのシームレスな統合
  • アクティブメンテナンス: 継続的な開発・サポート
  • 豊富なドキュメント: 充実した公式ドキュメントとサンプルコード

デメリット

  • JVM限定: JVMベースの環境でのみ動作
  • Android非対応: Androidには別ライブラリが必要
  • Auth0依存: Auth0エコシステムに特化した設計
  • 学習コスト: JWT仕様とセキュリティの理解が必要
  • デバッグ複雑性: トークン関連エラーの原因特定が困難な場合がある
  • 設定の複雑さ: 高度なセキュリティ設定時の複雑さ

主要リンク

書き方の例

基本的なセットアップ

<!-- Maven依存関係 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.5.0</version>
</dependency>
// Gradle依存関係
implementation 'com.auth0:java-jwt:4.5.0'

JWTトークンの作成(HMAC)

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;

public class JWTService {
    private static final String SECRET = "your-256-bit-secret";
    
    public String createToken(String userId, String email) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            String token = JWT.create()
                .withIssuer("my-app")
                .withSubject(userId)
                .withClaim("email", email)
                .withClaim("role", "user")
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + 3600000)) // 1時間
                .sign(algorithm);
            return token;
        } catch (JWTCreationException exception) {
            throw new RuntimeException("JWT作成エラー", exception);
        }
    }
}

JWTトークンの検証(HMAC)

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

public class JWTVerifier {
    private static final String SECRET = "your-256-bit-secret";
    
    public DecodedJWT verifyToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("my-app")
                .build();
            return verifier.verify(token);
        } catch (JWTVerificationException exception) {
            throw new RuntimeException("JWT検証エラー", exception);
        }
    }
    
    public boolean isTokenValid(String token) {
        try {
            verifyToken(token);
            return true;
        } catch (RuntimeException e) {
            return false;
        }
    }
    
    public String getUserIdFromToken(String token) {
        DecodedJWT jwt = verifyToken(token);
        return jwt.getSubject();
    }
    
    public String getEmailFromToken(String token) {
        DecodedJWT jwt = verifyToken(token);
        return jwt.getClaim("email").asString();
    }
}

RSA暗号化を使用したJWT

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAJWTService {
    private final RSAPrivateKey privateKey;
    private final RSAPublicKey publicKey;
    
    public RSAJWTService(String privateKeyPath, String publicKeyPath) 
            throws Exception {
        this.privateKey = loadPrivateKey(privateKeyPath);
        this.publicKey = loadPublicKey(publicKeyPath);
    }
    
    public String createRSAToken(String userId, String email) {
        try {
            Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
            return JWT.create()
                .withIssuer("secure-app")
                .withSubject(userId)
                .withClaim("email", email)
                .withClaim("scope", "read write")
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis() + 7200000)) // 2時間
                .sign(algorithm);
        } catch (JWTCreationException exception) {
            throw new RuntimeException("RSA JWT作成エラー", exception);
        }
    }
    
    public DecodedJWT verifyRSAToken(String token) {
        try {
            Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
            JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("secure-app")
                .build();
            return verifier.verify(token);
        } catch (JWTVerificationException exception) {
            throw new RuntimeException("RSA JWT検証エラー", exception);
        }
    }
    
    private RSAPrivateKey loadPrivateKey(String keyPath) throws Exception {
        String keyContent = new String(Files.readAllBytes(Paths.get(keyPath)))
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")
            .replaceAll("\\s", "");
        
        byte[] keyBytes = Base64.getDecoder().decode(keyContent);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return (RSAPrivateKey) factory.generatePrivate(spec);
    }
    
    private RSAPublicKey loadPublicKey(String keyPath) throws Exception {
        String keyContent = new String(Files.readAllBytes(Paths.get(keyPath)))
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .replaceAll("\\s", "");
        
        byte[] keyBytes = Base64.getDecoder().decode(keyContent);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return (RSAPublicKey) factory.generatePublic(spec);
    }
}

Spring Boot統合

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;

@Service
public class AuthenticationService {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private long jwtExpiration;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("authorities", userDetails.getAuthorities()
            .stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toList()));
        
        return createToken(claims, userDetails.getUsername());
    }
    
    private String createToken(Map<String, Object> claims, String subject) {
        Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
        
        return JWT.create()
            .withSubject(subject)
            .withIssuedAt(new Date())
            .withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration))
            .withClaim("authorities", (List<String>) claims.get("authorities"))
            .sign(algorithm);
    }
    
    public String extractUsername(String token) {
        return verifyToken(token).getSubject();
    }
    
    public List<String> extractAuthorities(String token) {
        return verifyToken(token).getClaim("authorities").asList(String.class);
    }
    
    public boolean isTokenExpired(String token) {
        return verifyToken(token).getExpiresAt().before(new Date());
    }
    
    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    
    private DecodedJWT verifyToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
        JWTVerifier verifier = JWT.require(algorithm).build();
        return verifier.verify(token);
    }
}

Kotlin実装例

import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.exceptions.JWTCreationException
import com.auth0.jwt.exceptions.JWTVerificationException
import com.auth0.jwt.interfaces.DecodedJWT
import java.util.*

class KotlinJWTService(private val secret: String) {
    
    fun createToken(
        userId: String,
        email: String,
        roles: List<String> = emptyList()
    ): String {
        return try {
            val algorithm = Algorithm.HMAC256(secret)
            JWT.create()
                .withIssuer("kotlin-app")
                .withSubject(userId)
                .withClaim("email", email)
                .withClaim("roles", roles)
                .withIssuedAt(Date())
                .withExpiresAt(Date(System.currentTimeMillis() + 3600000L))
                .sign(algorithm)
        } catch (exception: JWTCreationException) {
            throw RuntimeException("JWT作成エラー", exception)
        }
    }
    
    fun verifyToken(token: String): DecodedJWT {
        return try {
            val algorithm = Algorithm.HMAC256(secret)
            val verifier = JWT.require(algorithm)
                .withIssuer("kotlin-app")
                .build()
            verifier.verify(token)
        } catch (exception: JWTVerificationException) {
            throw RuntimeException("JWT検証エラー", exception)
        }
    }
    
    fun extractClaims(token: String): TokenClaims {
        val jwt = verifyToken(token)
        return TokenClaims(
            userId = jwt.subject,
            email = jwt.getClaim("email").asString(),
            roles = jwt.getClaim("roles").asList(String::class.java),
            issuedAt = jwt.issuedAt,
            expiresAt = jwt.expiresAt
        )
    }
    
    fun isTokenValid(token: String): Boolean {
        return try {
            verifyToken(token)
            true
        } catch (e: RuntimeException) {
            false
        }
    }
}

data class TokenClaims(
    val userId: String,
    val email: String,
    val roles: List<String>,
    val issuedAt: Date,
    val expiresAt: Date
)

リフレッシュトークン実装

@Component
public class RefreshTokenService {
    
    private final String accessSecret;
    private final String refreshSecret;
    private final long accessTokenExpiration;
    private final long refreshTokenExpiration;
    
    public RefreshTokenService(
            @Value("${jwt.access.secret}") String accessSecret,
            @Value("${jwt.refresh.secret}") String refreshSecret,
            @Value("${jwt.access.expiration}") long accessTokenExpiration,
            @Value("${jwt.refresh.expiration}") long refreshTokenExpiration) {
        this.accessSecret = accessSecret;
        this.refreshSecret = refreshSecret;
        this.accessTokenExpiration = accessTokenExpiration;
        this.refreshTokenExpiration = refreshTokenExpiration;
    }
    
    public TokenPair generateTokenPair(String userId, String email) {
        String accessToken = createAccessToken(userId, email);
        String refreshToken = createRefreshToken(userId);
        
        return new TokenPair(accessToken, refreshToken);
    }
    
    private String createAccessToken(String userId, String email) {
        Algorithm algorithm = Algorithm.HMAC256(accessSecret);
        return JWT.create()
            .withIssuer("app")
            .withSubject(userId)
            .withClaim("email", email)
            .withClaim("type", "access")
            .withIssuedAt(new Date())
            .withExpiresAt(new Date(System.currentTimeMillis() + accessTokenExpiration))
            .sign(algorithm);
    }
    
    private String createRefreshToken(String userId) {
        Algorithm algorithm = Algorithm.HMAC256(refreshSecret);
        return JWT.create()
            .withIssuer("app")
            .withSubject(userId)
            .withClaim("type", "refresh")
            .withIssuedAt(new Date())
            .withExpiresAt(new Date(System.currentTimeMillis() + refreshTokenExpiration))
            .sign(algorithm);
    }
    
    public TokenPair refreshAccessToken(String refreshToken) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(refreshSecret);
            JWTVerifier verifier = JWT.require(algorithm)
                .withIssuer("app")
                .withClaim("type", "refresh")
                .build();
            
            DecodedJWT jwt = verifier.verify(refreshToken);
            String userId = jwt.getSubject();
            
            // ユーザー情報を取得(データベースから)
            String email = getUserEmail(userId);
            
            return generateTokenPair(userId, email);
        } catch (JWTVerificationException e) {
            throw new RuntimeException("無効なリフレッシュトークン", e);
        }
    }
    
    private String getUserEmail(String userId) {
        // データベースからユーザー情報を取得
        return "[email protected]"; // 実装例
    }
    
    public static class TokenPair {
        private final String accessToken;
        private final String refreshToken;
        
        public TokenPair(String accessToken, String refreshToken) {
            this.accessToken = accessToken;
            this.refreshToken = refreshToken;
        }
        
        // getters
        public String getAccessToken() { return accessToken; }
        public String getRefreshToken() { return refreshToken; }
    }
}

JWTデコード(検証なし)

public class JWTDecoder {
    
    public void decodeTokenWithoutVerification(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            
            System.out.println("Header:");
            System.out.println("  Algorithm: " + jwt.getAlgorithm());
            System.out.println("  Type: " + jwt.getType());
            
            System.out.println("Payload:");
            System.out.println("  Issuer: " + jwt.getIssuer());
            System.out.println("  Subject: " + jwt.getSubject());
            System.out.println("  Audience: " + jwt.getAudience());
            System.out.println("  Expires At: " + jwt.getExpiresAt());
            System.out.println("  Not Before: " + jwt.getNotBefore());
            System.out.println("  Issued At: " + jwt.getIssuedAt());
            System.out.println("  JWT ID: " + jwt.getId());
            
            System.out.println("Custom Claims:");
            jwt.getClaims().forEach((key, claim) -> {
                if (!isStandardClaim(key)) {
                    System.out.println("  " + key + ": " + claim.asString());
                }
            });
            
        } catch (JWTDecodeException exception) {
            System.err.println("無効なJWTトークン: " + exception.getMessage());
        }
    }
    
    private boolean isStandardClaim(String claimName) {
        return Arrays.asList("iss", "sub", "aud", "exp", "nbf", "iat", "jti")
            .contains(claimName);
    }
}