Kotlinでjwks-rsa-javaを使ったJWKの取得方法

# JWKとは
JSON Web Keyとは暗号鍵を表現するためのJSONデータ構造のことで、ID Tokenの検証などに用いられる。
また複数のJWKのセットのことをJWKS（JWK Set）と呼ぶ。

# jwks-rsa-javaライブラリを使ってみる
[公式のGitHub](https://github.com/auth0/jwks-rsa-java)を参考に基本的な機能を作成してみます。

まずはGradleを使ってライブラリを取得しましょう。
またjava-jwtライブラリも使用するので入れておきます。

```gradle:gradle.build
implementation 'com.auth0:jwks-rsa:0.8.2'
implementation 'com.auth0:java-jwt:3.8.1'
```

## JWTからkey idの取得
公式のリポジトリにあるサンプル用のJWTから`key id(kid)`を取得しましょう。

```:サンプル用JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlJrSTVNakk1T1VZNU9EYzFOMFE0UXpNME9VWXpOa1ZHTVRKRE9VRXpRa0ZDT1RVM05qRTJSZyJ9.eyJpc3MiOiJodHRwczovL3NhbmRyaW5vLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1NjMyNTAxZjQ2OGYwZjE3NTZmNGNhYjAiLCJhdWQiOiJQN2JhQnRTc3JmQlhPY3A5bHlsMUZEZVh0ZmFKUzRyViIsImV4cCI6MTQ2ODk2NDkyNiwiaWF0IjoxNDY4OTI4OTI2fQ.NaNeRSDCNu522u4hcVhV65plQOiGPStgSzVW4vR0liZYQBlZ_3OKqCmHXsu28NwVHW7_KfVgOz4m3BK6eMDZk50dAKf9LQzHhiG8acZLzm5bNMU3iobSAJdRhweRht544ZJkzJ-scS1fyI4gaPS5aD3SaLRYWR0Xsb6N1HU86trnbn-XSYSspNqzIUeJjduEpPwC53V8E2r1WZXbqEHwM9_BGEeNTQ8X9NqCUvbQtnylgYR3mfJRL14JsCWNFmmamgNNHAI0uAJo84mu_03I25eVuCK0VYStLPd0XFEyMVFpk48Bg9KNWLMZ7OUGTB_uv_1u19wKYtqeTbt9m1YcPMQ
```

[こちらの記事](https://qiita.com/dddEn/items/48d2f2e2c8f98209e192)を参考にJWTをデコードし、kidを取得します。

```kotlin:
val token: String = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlJrSTVNakk1T1VZNU9EYzFOMFE0UXpNME9VWXpOa1ZHTVRKRE9VRXpRa0ZDT1RVM05qRTJSZyJ9.eyJpc3MiOiJodHRwczovL3NhbmRyaW5vLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1NjMyNTAxZjQ2OGYwZjE3NTZmNGNhYjAiLCJhdWQiOiJQN2JhQnRTc3JmQlhPY3A5bHlsMUZEZVh0ZmFKUzRyViIsImV4cCI6MTQ2ODk2NDkyNiwiaWF0IjoxNDY4OTI4OTI2fQ.NaNeRSDCNu522u4hcVhV65plQOiGPStgSzVW4vR0liZYQBlZ_3OKqCmHXsu28NwVHW7_KfVgOz4m3BK6eMDZk50dAKf9LQzHhiG8acZLzm5bNMU3iobSAJdRhweRht544ZJkzJ-scS1fyI4gaPS5aD3SaLRYWR0Xsb6N1HU86trnbn-XSYSspNqzIUeJjduEpPwC53V8E2r1WZXbqEHwM9_BGEeNTQ8X9NqCUvbQtnylgYR3mfJRL14JsCWNFmmamgNNHAI0uAJo84mu_03I25eVuCK0VYStLPd0XFEyMVFpk48Bg9KNWLMZ7OUGTB_uv_1u19wKYtqeTbt9m1YcPMQ"

val jwt = JWT.decode(token)

val keyId: String = jwt.getKeyId()
// keyId = RkI5MjI5OUY5ODc1N0Q4QzM0OUYzNkVGMTJDOUEzQkFCOTU3NjE2Rg
```

## issuerからjwksの取得
UrlJwkProviderを使って指定されたURLからjwksを取得します。

```kotlin:
val issuer: String = jwt.getIssuer()
// issuer = https://samples.auth0.com/

val issuer_path= ".well-known/jwks.json"
// こちらはリポジトリに記載されている。本来はドメイン提供者から指定されるもの。

val provider = UrlJwkProvider(URL(issuer + issuer_path))
// https://samples.auth0.com/jwks.well-known/jwks.jsonからjwksを取得する。
// 今回の場合はjwksのなかにjwkは一つしかない。

val jwk = provider.get(keyId)
// 取得したjwksの中からkidがkeyIdと同一のjwkを取得する。
```

## jwksをキャッシュとして保持する
今のままだとユーザからリクエストがあるたびにjwksを取得し直す事になってしまい、そのまま放置しておくと認証サーバからアクセス過多で弾かれかねません。
そこで`GuavaCachedJwkProviderjwks`を使ってjwksをキャッシュに保持しましょう。

```kotlin:
val http = UrlJwkProvider(URL(issuer + issuer_path))
val provider = GuavaCachedJwkProvider(http)

val jwk = provider.get(keyId)
```

キャッシュに保持しているjwks内に`keyId`と一致するkidが存在しない場合、`UrlJwkProvider`へとjwksを取得しにいきます。

デフォルトでは５個のキーを１０時間保持しますが、この設定を変えることも可能です。

## パブリックキーの生成とtokenの検証
jwkを取得できたのでパブリックキーを生成してみましょう。
jwkの中を見てみると、どうやらe値とn値しかないようなのでそれを使います。

```kotlin:
val publicKey: RSAPublicKey = jwk.publicKey as RSAPublicKey
```

たったこれだけでパブリックキーを取得できます。
ちなみに中ではこんなことをやっています。

```java:
KeyFactory kf = KeyFactory.getInstance(PUBLIC_KEY_ALGORITHM);
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(stringValue("n")));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64(stringValue("e")));
return kf.generatePublic(new RSAPublicKeySpec(modulus, exponent));
```

では早速tokenを検証してみましょう。
ちなみにサンプルのJWTはtokenの期限が2016年となっているので、**検証が成功することはありません。**

```kotlin:
val algorithm = Algorithm.RSA256(publicKey,null) // 署名の検証なので公開鍵のみ(非推奨)

val verifier = JWT.require(algorithm)
            .withIssuer("https://samples.auth0.com/")
            .build()

val result = verifier.verify(token)
```


# 参考文献
[auth0/jwks-rsa-java](https://github.com/auth0/jwks-rsa-java)
[JSON Web Key (JWK)](https://openid-foundation-japan.github.io/rfc7517.ja.html)
[auth0/java-jwt](https://github.com/auth0/java-jwt)
[Kotlinでjava-jwtを使ってTokenを見てみる](https://qiita.com/dddEn/items/48d2f2e2c8f98209e192)