Keycloak : JWT によるクライアント認証

株式会社 日立製作所　茂木昂士

# はじめに

OAuth/OpenID Connect では、アクセストークンを発行する Token リクエストの際に、リクエストを送信したクライアントが正しいか確認するためにクライアント認証を行っています。Keycloak の Client 画面で Credentials タブを開くと、`Client Authenticator` にデフォルトで`Client Id and Secret`というものが選択されています。これは、Client ID 及び Client Secret によってクライアントを認証するというものです。[RFC7523 : JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://tools.ietf.org/html/rfc7523)では、この Client ID と Client Secret による認証に代わる方法として、署名した JWT によるクライアント認証の方法が定義されています。

今回は Keycloak を利用して、JWT によるクライアント認証を試してみたいと思います。

# Keycloak での設定

まず、Keycloak で JWT を署名するための鍵を生成します。Client の Credentials タブを開き、`Client Authenticator`の項目で`Signed Jwt`を選ぶと、`Generate new keys and certificate` と `Import Certificate`というボタンが表示されます。

![client_setting.png](https://qiita-image-store.s3.amazonaws.com/0/273221/99331890-5519-99e8-2dbb-8ee03c6ce902.png)


今回は生成されたキーペアを利用したいので、`Generate new keys and certificate` を選択します。

![generate_private_key.png](https://qiita-image-store.s3.amazonaws.com/0/273221/0b52bd61-cb4a-e2d2-d645-6c31bf26a475.png)


キーストアの形式は、`JKS(Java KeyStore)`形式と`PKCS12`形式が選択できます。今回は`PKCS12`形式を選択しました。クライアント側のプログラムで利用しやすいほうを選択してください。そのほか必要な項目を埋めて Generate new keys ans certificate をクリックすると、キーペアが生成されるとともに、Private Key が保存されたキーストア`keystore.p12`がダウンロードされます。このキーストアにある Private Key を利用して、JWT に署名を付加することになります。

# クライアントプログラム側での対応

クライアントから Keycloak へ Token リクエストを行う際に、先ほど生成した Private Key で署名を行った JWT を送信します。その際の、署名を行う JWT の形式は [3. JWT Format and Processing Requirements](https://tools.ietf.org/html/rfc7523#section-3)に定義されています。

今回は下記の通りとしました。

```json
{
  "aud": "http://localhost:8080/auth/realms/sample/protocol/openid-connect/token",
  "exp": 1544091946,
  "jti": "7e2f184e-6e1b-415f-99d8-56ca6ccf6946",
  "iat": 1543227946,
  "iss": "http://localhost:3000/",
  "sub": "client"
}
```

JWT の各 Claim に関しては下記の通りです。

| #   | Claim | Value                                                                               |
| --- | ----- | ----------------------------------------------------------------------------------- |
| 1   | aud   | Token Endpoint の URL を指定する。                                                  |
| 2   | exp   | トークンの有効期限を設定。                                                          |
| 3   | jti   | JWT の ID で、 JWT 毎にユニークな値にする必要がある。今回は UUID を使用。           |
| 4   | iat   | トークンを署名した時刻を設定。                                                      |
| 5   | iss   | JWT を署名したクライアントの識別子。一般的にはクライアント側の URL 等が利用される。 |
| 6   | sub   | 自身の Client ID。Keycloak に登録されている ID を利用する。                         |

JWT への署名が完了したら、下記のパラメータを Token エンドポイントに送信します。

| #   | Key                   | Value                                                  |
| --- | --------------------- | ------------------------------------------------------ |
| 1   | redirect_uri          | http://localhost:3000/callback                         |
| 2   | grant_type            | authorization_code                                     |
| 3   | code                  | (Keycloak から送られた Code の値)                      |
| 4   | client_id             | client                                                 |
| 5   | client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
| 6   | client_assertion      | (署名した JWT)                                         |

特徴として、`client_secret`がなくなった代わりに、`client_assertion_type` と `client_assertion`が追加されています。`client_assertion_type`は`urn:ietf:params:oauth:client-assertion-type:jwt-bearer`を指定し、`client_assertion`には先ほど署名した JWT の値を指定してリクエストを実行します。

上記のリクエストによって Keycloak 側で 署名の検証、JWT の検証が行われ、値が正しければ Keycloak からアクセストークンが発行されます。

# RFC7523 を使用する利点

クライアント認証を行う際に`Client ID`と`Client Secret`を利用する方法では、どうしても通信経路に秘密情報である`Client Secret`が流れてしまいます。そのため、通信経路の盗聴などにより情報が漏えいしてしまう可能性があります。

一方、RFC7523 を利用すると JWT の情報が通信経路に流れますが、その情報には有効期限`exp`が指定されていますので、`Client Secret`が漏えいするよりも被害を抑えることができます。また Keycloak は、仕様では Optional となっている Replay Attack 防止(`jti` による JWT の再利用防止)が実装されているため、使用済みの JWT が漏えいしてもアクセストークンの発行はできなくなっています。

# まとめ

Keycloak ではセキュリティを高めるための様々な機能が実装されていて、システムのセキュリティ要件に応じて必要な機能を利用することができます。ただ、ドキュメント化されていない部分も多く、利用が難しいのが現状です。これからも色々な機能を利用して、情報を発信していきたいと思います。