Kotlinでjava-jwtを使ってTokenを見てみる

# JWTとは
JSON Web Tokenは属性情報（Claim）をJSONデータ構造で表現したトークンの仕様のことです。
よくOAuthでのID Tokenなどに用いられています。
署名されているため改ざんをチェックができ、URLに含むことができる文字のみで構成されている特徴を持ちます。

# java-jwtを使ってみる
[公式のGitHub](https://github.com/auth0/java-jwt)を参考に基本的な機能を使ってみましょう。

まずはGradleでライブラリを取得します。

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

また今回はサンプルとして提示されている、こちらのTokenを使用します。

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE
```

## Tokenのデコード
まずはTokenをデコードしてみます。

```kotlin:
val token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"

try {
    val jwt = JWT.decode(token)
} catch (exception: JWTDecodeException) {
    //無効なトークンの場合の処理
}
```
トークンが無効であるか、ヘッダーまたはペイロードがJSONではない場合、`JWTDecodeException`が返されます。

さてデコードした結果を見てみましょう。

```json:
{
"header":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9",
"payload":"eyJpc3MiOiJhdXRoMCJ9",
"contentType":null,
"notBefore":null,
"algorithm":"HS256",
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE",
"keyId":null,
"claims":{
    "iss":{
        "null":false
        }
    },
"subject":null,
"audience":null,
"expiresAt":null,
"issuedAt":null,
"issuer":"auth0",
"id":null,
"type":"JWS",
"signature":"AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"}
```

## Header Claimの取得
無事Tokenの中身を取得することができましたが、このままJSONとして使用するのは不便です。
そこでjava-jwtにはそれぞれ値をString型として取得するメソッドを使用してみましょう。

```kotlin:
val algorithm: String = jwt.getAlgorithm()

val type: String = jwt.getType()

val contentType: String = jwt.getContentType()

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

それ以外にも任意のHeader Claimの値を取得することもできます。

```kotlin:
val claim: Claim = jwt.getHeaderClaim("owner")
// 任意のHeader Claim（今回はowner）を取得
```

また`claim.inNull()`を使用することでHeader Claimの中身がNullかどうかの判定を行うことができます。

## Payload Claimの取得
さてHeaderと同様にPayloadに関してもそれぞれのClaimを取得してくれる便利なメソッドがあります。

```kotlin:
val issuer: String = jwt.getIssuer()

val subject: String = jwt.getSubject()

val audience: List<String> = jwt.getAudience()

val expiresAt: Date = jwt.getExpiresAt()

val notBefore: Date = jwt.getNotBefore()

val issuedAt: Date = jwt.getIssuedAt()

val id: String = jwt.getId()
```

同様に任意のPayload Claimを取得することができます。

```kotlin:
val claims: Map<String, Claim> = jwt.getClaims()    
// KeyはClaimの名前
val claim: Claim = claims.get("isAdmin");
// 任意のPayload Claim（今回はisAdmin）を取得
```

もしくはこのような形でも同様な結果になります。

```kotlin:
val claim: Claim = jwt.getClaim("isAdmin");
```

## Tokenの検証
java-jwtにはあらかじめアルゴリズムなどを決めておき、そこにTokenを引数として指定するだけで自動で検証を行ってくれる機能があります。

今回はHS256だったのでアルゴリズムのメソッドにはHMAC256を指定します。

```kotlin:
val token: String = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"

try {
    val algorithm: Algorithm = Algorithm.HMAC256("secret")
    val verifier: JWTVerifier = JWT.require(algorithm)
        .withIssuer("auth0")
        .build()
// verifierは何度でも再利用することができるインスタンスとなります。

    val jwt: DecodedJWT = verifier.verify(token)

} catch (exception: JWTCreationException){
    // 署名が無効、もしくはClaimが変換できない場合の処理。
}
```

検証に成功するとデコードされたTokenがJSONで返されます。


# 参考文献
[JWT(JSON Web Token)の仕組みと使い方まとめ](https://webbibouroku.com/Blog/Article/jwt)
[auth0/java-jwt](https://github.com/auth0/java-jwt)
[KotlinとJava-JWTを使用したKtorでのJSON Webトークン検証](https://www.scottbrady91.com/Kotlin/JSON-Web-Token-Verification-in-Ktor-using-Kotlin-and-Java-JWT)
[OpenID Connect – Javaでidトークンを検証する方法](https://codeday.me/jp/qa/20190304/361573.html)
[【OpenID Connect】公開されているJWKを使用してIDTokenを検証するやり方](http://www.sassy-blog.com/entry/20171003/1506991888)