Open5

Nimbus JWT + JOSE の使い方

com4dccom4dc

Nimbus JWT + JOSE

Spring Security の Resource Server サポート等の OAuth 2.0, OpenID Connect のクライアントを利用する際に内部で利用されている実装。普段はあまり見ないが、JWTや JWKSet 等の具体的な実装コードがあるので見ると色々勉強になる

https://connect2id.com/products/nimbus-jose-jwt/examples

com4dccom4dc

JWKSet の構成方法

通常 JWKSet は外部プロパティでURLが渡されるなどして、自動的に取得し構成される。しかし諸事情があって内部のファイルで持ちたい場合などは、以下のようなコードで対応できる。

// Load JWK set from JSON file, etc.
JWKSet jwkSet = JWKSet.load(new File("/var/server/jwkset.json");

自分はこれに気づかず、以下を実装。

val json = Files.readString(Paths.get(ClassLoader.getSystemResource("jwk.json").toURI()));
return JWKSet.parse(json)

Immutable にする

JWKSet を Immutable にする

// Create JWK source backed by a JWK set
JWKSource keySource = new ImmutableJWKSet(jwkSet);
com4dccom4dc

署名付きJWT

https://connect2id.com/products/nimbus-jose-jwt/examples/jws-mint-framework

val jwkSource: JWKSource<SecurityContext> = ImmutableJWKSet(jwkSet())
val minter: ConfigurableJWSMinter<SecurityContext> = DefaultJWSMinter()
minter.jwkSource = jwkSource

val header = JWSHeader.Builder(JWSAlgorithm.RS256)
    .type(JOSEObjectType.JWT)
    .build()

// Create JWT
val nowInstant = Instant.now()
val jwtClaims = JWTClaimsSet.Builder()
    .issuer("https://openid.net")
    .subject("alice")
    .audience(Arrays.asList("https://app-one.com", "https://app-two.com"))
    .expirationTime(Date(nowInstant.plusSeconds(600).toEpochMilli())) // expires in 10 minutes
    .notBeforeTime(Date(nowInstant.toEpochMilli()))
    .issueTime(Date(nowInstant.toEpochMilli()))
    .jwtID(UUID.randomUUID().toString())
    .build()

val jwsObject = minter.mint(header, jwtClaims.toPayload(), null)
return jwsObject.serialize()
  • SecurityContextcom.nimbusds.jose.proc.SecurityContext であるので、 spring.security パッケージのものと混同しないように
com4dccom4dc

JSON Web Encryption

https://www.rfc-editor.org/info/rfc7516

暗号化したJSONオブジェクト。当然ながら普通にパースしても何のことやらな内容

val jwk = jwkSet()

// Create JWT
val nowInstant = Instant.now()
val jwtClaims = JWTClaimsSet.Builder()
.issuer("https://openid.net")
.subject("alice")
.audience(Arrays.asList("https://app-one.com", "https://app-two.com"))
.expirationTime(Date(nowInstant.plusSeconds(600).toEpochMilli())) // expires in 10 minutes
.notBeforeTime(Date(nowInstant.toEpochMilli()))
.issueTime(Date(nowInstant.toEpochMilli()))
.jwtID(UUID.randomUUID().toString())
.build()

// Request JWT encrypted with RSA-OAEP-256 and 128-bit AES/GCM
val header = JWEHeader(
JWEAlgorithm.RSA_OAEP_256,
EncryptionMethod.A128GCM
)

val jwt = EncryptedJWT(header, jwtClaims)

// Create an encrypter with the specified public RSA key
val encrypter = RSAEncrypter(jwk.getKeyByKeyId("1").toRSAKey())
jwt.encrypt(encrypter)

return jwt.serialize()
com4dccom4dc

Verify JWT

署名付き JWT の検証

try {
    val jwkSource = ImmutableJWKSet<SecurityContext>(jwkSet())

    // JwtProcessor
    val jwtProcessor = DefaultJWTProcessor<SecurityContext>()
    jwtProcessor.jwsTypeVerifier = DefaultJOSEObjectTypeVerifier(JOSEObjectType.JWT)
    // jwtProcessor.jweTypeVerifier // JWE の Verifier もいる

    val keySelector = JWSVerificationKeySelector(JWSAlgorithm.RS256, jwkSource)
    jwtProcessor.jwsKeySelector = keySelector

    jwtProcessor.jwtClaimsSetVerifier =
        DefaultJWTClaimsVerifier(
            JWTClaimsSet.Builder().issuer(ISSUER).build(),
            setOf("sub", "iat", "exp", "jti", "KEY1", "KEY2"))

    val claimsSet: JWTClaimsSet = jwtProcessor.process(token, null)
    logger.debug("Verified Claimset: $claimsSet")

    // Claims から各種カスタム情報を取得
    val hoge = claimsSet.claims.get("KEY1") as String
    val fuga = claimsSet.claims.get("KEY2") as String

    return Pairs(hoge, fuga)
} catch (e: Exception) {
    when (e) {
    is ParseException, is BadJOSEException, is JOSEException -> {
        // Verify 失敗
        throw FailedVerifyJwtException("Failed verify Jwt Token", e)
    }
    else -> {
        throw UnexpectedVerifyJwtException("Unexpected Error", e)
    }
    }
}
  • JWT の正しさや有効期限を確認し、カスタム Claims の値を取得して返してる
  • Verify した時点で Claims 等の取得ができてるので、そのまま利用した
  • jwtProcessor.jweTypeVerifier もあるため、 JWE も検証できそう

https://connect2id.com/products/nimbus-jose-jwt/examples/validating-jwt-access-tokens#framework