JWKとは
JSON Web Keyとは暗号鍵を表現するためのJSONデータ構造のことで、ID Tokenの検証などに用いられる。
また複数のJWKのセットのことをJWKS(JWK Set)と呼ぶ。
jwks-rsa-javaライブラリを使ってみる
公式のGitHubを参考に基本的な機能を作成してみます。
まずはGradleを使ってライブラリを取得しましょう。
またjava-jwtライブラリも使用するので入れておきます。
implementation 'com.auth0:jwks-rsa:0.8.2'
implementation 'com.auth0:java-jwt:3.8.1'
JWTからkey idの取得
公式のリポジトリにあるサンプル用のJWTからkey id(kid)
を取得しましょう。
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IlJrSTVNakk1T1VZNU9EYzFOMFE0UXpNME9VWXpOa1ZHTVRKRE9VRXpRa0ZDT1RVM05qRTJSZyJ9.eyJpc3MiOiJodHRwczovL3NhbmRyaW5vLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1NjMyNTAxZjQ2OGYwZjE3NTZmNGNhYjAiLCJhdWQiOiJQN2JhQnRTc3JmQlhPY3A5bHlsMUZEZVh0ZmFKUzRyViIsImV4cCI6MTQ2ODk2NDkyNiwiaWF0IjoxNDY4OTI4OTI2fQ.NaNeRSDCNu522u4hcVhV65plQOiGPStgSzVW4vR0liZYQBlZ_3OKqCmHXsu28NwVHW7_KfVgOz4m3BK6eMDZk50dAKf9LQzHhiG8acZLzm5bNMU3iobSAJdRhweRht544ZJkzJ-scS1fyI4gaPS5aD3SaLRYWR0Xsb6N1HU86trnbn-XSYSspNqzIUeJjduEpPwC53V8E2r1WZXbqEHwM9_BGEeNTQ8X9NqCUvbQtnylgYR3mfJRL14JsCWNFmmamgNNHAI0uAJo84mu_03I25eVuCK0VYStLPd0XFEyMVFpk48Bg9KNWLMZ7OUGTB_uv_1u19wKYtqeTbt9m1YcPMQ
こちらの記事を参考にJWTをデコードし、kidを取得します。
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を取得します。
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をキャッシュに保持しましょう。
val http = UrlJwkProvider(URL(issuer + issuer_path))
val provider = GuavaCachedJwkProvider(http)
val jwk = provider.get(keyId)
キャッシュに保持しているjwks内にkeyId
と一致するkidが存在しない場合、UrlJwkProvider
へとjwksを取得しにいきます。
デフォルトでは5個のキーを10時間保持しますが、この設定を変えることも可能です。
パブリックキーの生成とtokenの検証
jwkを取得できたのでパブリックキーを生成してみましょう。
jwkの中を見てみると、どうやらe値とn値しかないようなのでそれを使います。
val publicKey: RSAPublicKey = jwk.publicKey as RSAPublicKey
たったこれだけでパブリックキーを取得できます。
ちなみに中ではこんなことをやっています。
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年となっているので、検証が成功することはありません。
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
JSON Web Key (JWK)
auth0/java-jwt
Kotlinでjava-jwtを使ってTokenを見てみる