「認証と認可 Keycloak 入門」を Quarkus 版 Keycloak で検証！ WildFly 版との変更点まとめ

# はじめに
初めまして。Keycloak 学習中の ahra@ITdo です。

今回私は Keycloak の学習のために、入門書としてよく紹介されるこちらの書籍を読みました。
>認証と認可　Keycloak入門 OAuth/OpenID Connectに準拠したAPI認可とシングルサインオンの実現（リックテレコム）
>中村雄一、和田広之、田村広平、田畑義之、青柳隆、渡辺竜二、奥浦航、相田洋志　著
>https://www.ric.co.jp/book/development/detail/2081

基本的概念である「認証」や「認可」の用語について、また OAuth・OIDC の仕組みの詳細な解説など基礎的内容から詳細に書かれており、私のような初学者にとっては最適な書籍でした。

しかし、「実践編」をはじめとした Keycloak を実際に操作する部分では、書籍で使用している Keycloak 15.0.2 から執筆時点での最新である 25.0.6 の間で、起動方法の変更や設定画面の刷新など大きな開きが少なからずあり、検証に苦労しました。
この記事では、そんな書籍との相違点を中心にまとめています。
Quarkus 版 Keycloak を使って検証する方が、「設定がどこにあるかわからない」「うまく動かない」と感じた際に参照していただけると幸いです。

（ちなみに書籍で使用している 15.0.2 は [Keycloak の GitHub](https://github.com/keycloak/keycloak/releases/15.0.2) から入手できるので、書籍と同一の内容で検証されたい方はリンクからどうぞ）

# 目次
[第1章 Keycloakを理解するための第一歩](#第1章-keycloak-を理解するための第一歩)
[第4章 Keycloak の基礎を理解する](#第4章-keycloak-の基礎を理解する)
[第5章 OAuth に従った API 認可の実現](#第5章-oauth-に従った-api-認可の実現)
[第6章 SSO を実現する](#第6章-sso-を実現する)
[第7章 さまざまな認証方式を用いる](#第7章-さまざまな認証方式を用いる)
[第8章 Keycloak のカスタマイズ](#第8章-keycloak-のカスタマイズ)
[第9章 Keycloak の非機能面の考慮ポイント](#第9章-keycloak-の非機能面の考慮ポイント)


# 第1章 Keycloak を理解するための第一歩

### 環境構築
執筆時点の最新バージョンである **Keycloak 25.0.6** を使用します。
OSには **Ubuntu 24.04 LTS** を使用しています。

#### Open JDK のインストール
Keycloak 25.0.6 の動作には Java 21 が必要です。
今回は openjdk を使います。
`sudo apt install openjdk-21-jdk` コマンドでインストールします。

#### Keycloak のインストール
この手順に変更はありません。
[Keycloak公式 ダウンロードページ](https://www.keycloak.org/downloads)から圧縮フォルダをダウンロードし、任意の場所に展開してください。
以降、この展開したディレクトリを`[KEYCLOAK_HOME]`と呼びます。

#### Keycloak の起動 
大きな変更点の一つがこの部分です。
Quarkus 版の Keycloak には**本番モード**と**開発モード**が存在します。
本番モードは HTTPS をデフォルトで強制するなど、検証には不便です。
この記事では基本的に開発モードで起動します。
開発モードでの起動は、
```
cd [KEYCLOAK_HOME]
./bin/kc.sh start-dev
```
によって可能です。
```
WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.
```
上記のメッセージがコンソールに出力されたら起動完了です。
検証を進めましょう。

#### 管理コンソールへのアクセス
Quarkus 版の Keycloak では、デフォルトのコンテクストパスが`/auth`から`/`に変更されています。
書籍では`http://localhost:8080/auth/`にアクセスするよう記載されていますが、Quarkus 版では`http://localhost:8080/`にアクセスします。
今後の内容にも影響しますので、覚えておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/36e7bd29-4985-8bca-3ed7-75db9bb04e45.png)

管理コンソールにアクセスすると、ウェルカムページではなく管理者ユーザー作成画面が表示されます。
この差異は気にせず、管理者ユーザーを作成しましょう。

#### ユーザーの追加 → アカウント管理コンソールの表示
ユーザーの追加の手順について大きな差異はありませんが、手順通りユーザー名とパスワードのみを設定した場合、初回認証時に
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/f6c12c58-924b-a709-82cf-df9d3e7ce4f7.png)
このようにメールアドレス、姓、名の入力を求められます。
これを要求されたくない場合は、アカウント管理コンソール > Realm settings > User profile から対象の属性を選択し、「Required field」を Off にしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/2fc118fe-0a8b-e677-ca4f-7f188ab3a929.png)

#### 日本語化
Realm settings の Localization タブから設定します。
デフォルトでは「Supported locales」に English しか設定されていないので、手動で追加しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/ee0db14c-f7ca-fe4e-6066-4cb5be63b00c.png)

# 第4章 Keycloak の基礎を理解する

#### レルムロールの設定
レルムロールの作成手順に大きな変更はありません。
しかし、デフォルトロールの設定方法が大きく変わっています。
Keycloak 25.0.6 では、Realm settings > User registration タブにデフォルトロールの設定があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/eba3de77-9341-5795-76be-0f01c8755a52.png)
レルムロールを作成したら、この画面の「Assign role」ボタンを押して、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/50bb11c2-9c47-cd9d-097a-0e66d5f37da3.png)

表示されたアサイン画面の左上プルダウンメニューから「Filter by realm roles」を選択し、一覧から割り当てるようにしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/6359fcb4-b444-ec6a-42f2-329f6c38494e.png)
削除する際は該当ロールの右側ケバブメニューをクリックし、「Unassign」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/86098b69-7add-f4c5-6b12-c65e6856fb4c.png)

クライアント、ユーザーへのロールの割り当てについては割愛します。書籍に沿って設定画面を開き、上記の方法で設定してください。

#### クライアントごとに使えるロールの設定
こちらも大きな変更点です。
Clients > 該当のクライアント > Client scopes タブより、`(クライアントID)-dedicated`というクライアントスコープを見つけます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/e28ab3db-99e0-fb32-637b-54aa4f5ef63a.png)
そして、そのクライアントスコープのリンクをクリックし表示される画面の Scope タブから設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/ca5e9651-212e-6eac-59f3-4e227b1f6f54.png)

設定手順については他のロールの割り当て手順と差異がないため割愛します。

#### グループ
画面の差異は大きいですが、基本的概念に差異がないため割愛します。
書籍では Users > (該当ユーザー) > Groups タブから行うグループへのマッピングが、Groups > 該当グループ > Members タブからユーザーの一覧を見ながらできるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/b255a8c6-22d9-dbfe-a474-cdc3fa83ec62.png)

#### プロトコルマッパーの設定
こちらにも大きな変更が入っています。
[クライアントごとに使えるロールの設定](1-クライアントごとに使えるロールの設定)と同じように、Clients > 該当のクライアント > Client scopesタブより、`(クライアントID)-dedicated`をクリックし、今度は Mappers タブから「Add mapper」で設定しましょう。
「From predefined mappers」からは用意されているプロトコルマッパーを、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/e45fa0e2-5c84-12de-23ae-8099497d14d5.png)
「By configuration」からは Mapper Type を選択しての詳細な設定が可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/d79f95fa-567b-d154-82d3-c065ce247f52.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/513e4e5c-9375-b2d8-a3be-7fff0394de2e.png)

#### セッションの有効期間の設定
書籍では Realm settings > Tokens タブから設定すると記載がありますが、Keycloak 25.0.6 ではリフレッシュトークン、アクセストークン、アクショントークンの設定しかありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/cea53333-f3d2-0955-2474-e7cb74430b8a.png)
SSO session、Cient session、Offline session に関する設定は、Sessions タブから行いましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/89f077b8-6b01-b59f-6503-31464f9581ba.png)
また、クライアントごとのセッションの有効期間の設定は、Client > (該当のクライアント) > Advanced タブから行いましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/52795ad8-0d5e-f459-583b-a24b58276d2c.png)

# 第5章 OAuth に従った API 認可の実現

#### サンプルクライアント・サンプルリソースサーバーのセットアップ
デフォルトのコンテクストパスが変わっているので、両サーバーの定義ファイル(appliction.properties)にある、以下のような定義からは`/auth`を削っておきましょう。
```diff
- clientapp.config.authorization-endpoint=http://localhost:8080/auth/realms/demo-api/protocol/openid-connect/auth
+ clientapp.config.authorization-endpoint=http://localhost:8080/realms/demo-api/protocol/openid-connect/auth
```
以降も、各サーバー用の定義ファイルにて`/auth`を削除しましょう。
#### クライアントの設定
設定画面が刷新されているため、書籍記載通りの設定項目名ではなくなっています。
書籍では「Access Type」と記載されている、クライアントが Confidential か Public かを選択する項目は、「Client authentication」という名称に変わっています。
両サーバーとも Confidential クライアントなので、On にしておきましょう。
また、サンプルクライアントでは認可サービスを利用するため、「Authorization」を On にしましょう。
Authenticaton flow については「Standard flow」(認可コードフロー)のみが On になっていればよいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/e24d3894-520b-3c94-2c8b-e88b6c9b0371.png)
最後に、登録画面にはなかった「Consent Required」を Settings タブから On にしておきましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/a29ed577-4b34-5b8d-1a13-95e4dd95e89e.png)

# 第6章 SSO を実現する

## Java アダプターを用いた SSO
現在のKeycloakリリースでは多くのクライアントアダプターが非推奨または廃止されています。
そのため検証で使用した25.0.6では、書籍が紹介する Tomcat アダプターは提供されていません。
また、入手できる最新バージョンの Tomcat アダプターである 24.0.5 用アダプターでは、`keycloak-policy-enforcer`が[アダプターのコードベースから分離された影響](https://www.keycloak.org/docs/latest/release_notes/index.html#keycloak-21-1-0)か、ダウンロードしたものをそのまま使用すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/5be55b80-b632-a194-282a-4fc2f4012bbc.png)
このように`keycloak-policy-enforcer`に含まれているクラスが見つからず、動作しません。
（他の要因の可能性も十分ありますが、考えられる理由として掲載します。）
したがって今回は、`keycloak-policy-enforcer`の分離前にリリースされた、21.0.0 版の Tomcat アダプターを使用します。

#### Keycloak サーバーの設定
Keycloak OIDC JSON を入手する手順と、21.0.0 用アダプターに関する互換性の設定のみ紹介します。
###### 互換性の設定
[Keycloak 23.0.0から追加された](https://www.keycloak.org/docs/25.0.6/upgrading/#migrating-to-23-0-0)、[OAuth 2.0 Authorization Server Issuer Identifier in Authorization Response](https://www.ietf.org/archive/id/draft-meyerzuselhausen-oauth-iss-auth-resp-00.html)により、デフォルトではリダイレクトで返す認可レスポンスに認可サーバーの識別子を追加するようになりました。
Tomcat アダプターでは、リダイレクト URI を動的に生成するため、そのままではリダイレクト URI に認可サーバーの識別子を付けたままトークンリクエストしてしまい、失敗します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/8342a55f-e583-82d7-874f-2e5683fb84dd.png)
回避のため、Clients > tomcat-examples > Advanced タブから OpenID Connect Compatibility Modes の「Execlude Issuer From Authentication Response」を On にしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/7c2c76df-55e2-ec54-650b-408472eaf3f9.png)
###### Keycloak OIDC JSON の入手
クライアントの設定が完了したら、Credential タブに移って右上の「Action」ドロップダウンメニューから、「Download adapter config」を押下して入手できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/1d4dc5ff-e2b1-b540-53f4-abd3a11a0df4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/1d7cfc0e-2313-ea00-d72f-329ed21cf17d.png)
ダウンロードしたら、`policy-enforcer`に関する定義の部分を削除しましょう。（アダプターが非対応なのか入れっぱなしだと Tomcat が起動しません）
今回は中央集権型認可サービスを使用しないので、削除しても問題ありません。
```diff
{
  "realm": "demo",
  "auth-server-url": "http://localhost:8080/",
  "ssl-required": "none",
  "resource": "tomcat-examples",
  "verify-token-audience": true,
  "credentials": {
    "secret": "LvJ17jj2JbJeqrE2VoZC5uhlaphEXeWo"
 },
+  "confidential-port": 0
-  "confidential-port": 0,
-  "policy-enforcer": {
-    "credentials": {}
-  }
}
```

## リバースプロキシを用いた SSO
#### mod_auth_openidc について
GitHub のリポジトリが変わっています。
https://github.com/OpenIDC/mod_auth_openidc

Debian 系のディストリビューションであれば`apt`のリポジトリにあるので、`sudo apt install libapache2-mod-auth-openidc`でインストール可能です。
#### クライアントの設定について
reverse-proxy-oidc クライアントの設定にて、書籍では「Valid Redirect URIs」に「ログアウト後にリダイレクトされるURI」として`http://localhost:18080/reverse-proxy-app/`を設定していますが、25.0.6 では専用の項目として作成された「Valid post logout redirect URIs」に設定しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/fce8dc81-f120-7068-8960-233dfafd22ed.png)

## JavaScript アダプターを用いた SSO
Java 21 の環境では、書籍提供のリソースサーバー用ソースコードのビルドができませんでした。
そのため今回は Java 8 をインストールし、環境を切り替えてからビルドします。
`sudo apt install openjdk-8-jdk`で Java 8 用 JDK を入手し、以下のコマンドを打って出現した一覧からインストールした Java 8 用 JDK を選択、
```
$ sudo update-alternatives --config java
alternative java (/usr/bin/java を提供) には 4 個の選択肢があります。

  選択肢    パス                                          優先度  状態
------------------------------------------------------------
  0            /usr/lib/jvm/java-21-openjdk-amd64/bin/java      2111      自動モード
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1111      手動モード
  2            /usr/lib/jvm/java-21-openjdk-amd64/bin/java      2111      手動モード
  3            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      手動モード
* 4            /usr/lib/jvm/jdk-20/bin/java                     10        手動モード

現在の選択 [*] を保持するには <Enter>、さもなければ選択肢の番号のキーを押してください: 3
update-alternatives: /usr/bin/java (java) を提供するためにマニュアルモードで /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java を使います
```
その後、
```
$ java -version
openjdk version "1.8.0_422"
OpenJDK Runtime Environment (build 1.8.0_422-8u422-b05-1~24.04-b05)
OpenJDK 64-Bit Server VM (build 25.422-b05, mixed mode)
```
にて切り替わっていることを確認して、`spa-resource-server`に`cd`、`mvn clean install`しましょう。
これで`spa-resource-server/target/`に`spa-resource-server.war`が作成されていると思います。
作成されていることを確認後、同じ手順で Java 21 の環境に戻しておきましょう。
# 第7章 さまざまな認証方式を用いる
## WebAuthn による認証の設定
#### WebAuthn ポリシーの設定
大きく変わりはありませんが、Authentication > Policies > WebAuthn Policy に移されています。
また、書籍では Require Resident Key の名称で掲載されている項目は、「Require discoverable credential」に変更されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/4dab55f6-ffe1-e69f-848e-1d6e6c491646.png)
#### 認証フローの設定
既存の認証フローのコピーはケバブメニューの「Duplicate」から行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/43f291a4-9755-3c47-d4c9-4e187bd02aa1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/02a6d78e-56cf-25e6-87de-7f958f7c31e0.png)
フローの設定画面では、「Add sub-flow」ボタンで作った Conditional 2FA サブフローをドラッグし webauthn for browser forms の子になるよう操作して、フローの関係を設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/875fa701-3af2-4f16-6e69-917af7043a6e.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/26eb07c0-cf0b-d445-e0e3-4244469f8a00.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/45989baa-1124-727f-207b-b50ea057d61f.png)
それ以外は書籍の内容に沿って設定できます。
#### 番外 Google Chrome の仮想認証システムを用いた、仮想認証器による検証
使用した環境では Yubikey を認証器として登録することができなかったため、表題の通り仮想認証器を使用しました。
Google Chrome をインストールし、デベロッパーツールを開きます。
右上のケバブメニュー > More tools > WebAuthn を選択し、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/f1710a65-10a2-20a6-99d2-3ca56e6581a0.png)
出現した WebAuthn タブの「Enable virtual authenticatior environment」をオンにして、以下の画面にします。
各設定の完了後、「Add」をクリックすると仮想認証器が作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/c3ef39d8-5b0b-e7e2-2bef-a9d3c8b5a1e8.png)

## 統合 Windows 認証
書籍では AD に参加した Windows マシン上で Keycloak を動作させ、そのマシンからアカウント管理コンソールにアクセスして動作確認をしていると考えられますが、今回は Ubuntu 上で Keycloak を動かす構成で検証しました。
アカウント管理コンソールにアクセスする Windows マシンの役割はドメインコントローラーが持ちます。
ドメイン名`Keycloak.test`で Active Directory を構成しているため一部書籍と内容が異なりますがご了承ください。

#### 前提
keytab ファイルに登録する SPN (サービスプリンシパル名)、Keycloak に設定する SPN、アカウント管理コンソールにアクセスする際に指定する FQDN をすべて同一に設定しなければ、Kerberos 認証は正常に動作しません。
また、SPN に含まれるホスト名が A レコードと PTR レコードの両方に登録されている必要があります。
作業の前に DNS への登録状況を確認しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/ff93b59d-3ae0-0a19-6b66-e19619cf5a1b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/bc658caf-668d-40fb-1d05-c9729174d5a8.png)

#### 連携用ユーザーの登録
連携用ユーザーを登録したら、必ずプロパティ > アカウント > アカウントオプションより、「このアカウントで Kerberos AES 256 ビット暗号化をサポートする」にチェックを入れてください。
keytab ファイルの作成時、 `-crypto`オプションに`AES256-SHA1`以外を指定した場合、そのアルゴリズムに合わせた項目をチェックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/9c0be94a-3826-3e19-cb8e-5036670d1c85.png)

#### Keytab ファイルの作成と Keycloak への設定
サービスプリンシパル名には Keycloak サーバーの FQDN を指定してください。
```
ktpass -out demo.keytab -princ HTTP/keycloak-test.keycloak.test@KEYCLOAK.TEST -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1 -mapuser keycloak -pass p@sw0rd
```
Keycloak への設定時も、Keycloak サーバーの FQDN を指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/70c0e5d3-a67b-19b2-5bde-9e3de7df3e51.png)
#### ブラウザの設定 → 動作検証
ローカルイントラネットゾーンに登録する際にも Keycloak サーバーの FQDN を指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/ee63f615-1066-8faa-bec1-f3e407444aaf.png)
アカウント管理コンソールへのアクセスも、Keycloak サーバーの FQDN を指定して行ってください。 
今回の設定例では `http://keycloak-test.keycloak.test:8080/realms/demo/account`になります。
「user001」でログインしている状態の Windows マシンからアクセスすると、そのままログイン状態のアカウント管理コンソールが開きます。（書籍記載の「Sign In」ボタンのあるページは 25.0.6 ではありません）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/7ee17bf0-e75f-fa46-6b44-c550810843fd.png)


# 第8章 Keycloak のカスタマイズ
#### 管理 CLI
Quarkus 版の Keycloak では管理 CLI が刷新されています。
管理 CLI のシェルは `[KEYCLOAK_HOME]/bin/kcadm.sh`です。
使用する際には認証が必要なので、起動時に認証情報を入力しましょう。
```
$ kcadm.sh config credentials --server http://localhost:8080 --realm master --user test --password (パスワード)
```
管理 CLI を使った各操作については、公式サーバー管理ガイドをご覧ください。
https://www.keycloak.org/docs/latest/server_admin/#admin-cli
#### Hostname SPI プロバイダー
Quarkus 版では起動オプション、環境変数、定義ファイル`keycloak.conf`のいずれかに指定する方法で設定します。
書籍記載の内容のように、常に固定の URL を用いて識別子を構築したい時は、`hostname`オプションで指定します。（下記は起動オプションで設定した例）
```
./bin/kc.sh start-dev --hostname keycloak-test.keycloak.test
```
#### Metrics SPI プロバイダー
jarファイルを配置するディレクトリは`[KEYCLOAK_HOME]`/providers`に変更されました。
また、ホットデプロイが廃止されたため、プロバイダーの追加には Keycloak の再起動が必要になりました。

## Twilio 用 Authenticator SPI の検証
度重なるバージョンアップにより、書籍が提供する Authenticator は 25.0.6 では動作しませんでした。
依存関係の変更、複数個所のコードの書き直しが必要となったため、今回の Authenticator SPI を使った検証は断念しました。
そのため完全な検証は行えていませんが、検証の途中で発見した差異や、Twilio についての補遺を記載します。
#### Twilio の設定
###### 電話番号の購入
Twilio のアカウントを作成したら、まず電話番号を購入しましょう。
ここで注意が必要なのは、**米国の**電話番号を購入する必要があるということです。
Twilio で購入できる日本の電話番号からは SMS を送信できませんが、米国の電話番号であれば米国以外のすべての電話番号に SMS を送信できます。
Account Dashboard > Phone Numbers > Manage > Buy a number から、Country を米国にセットして番号を購入しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/099938b8-a814-9ea2-0887-0820b07dfca1.png)
###### API キーの作成
Account Dashboard 画面の下部にある、「Go To API Keys」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/a1d76b2e-a3fb-ec39-352e-6f92a3e08c55.png)
次の画面の「Create API Key」ボタンをクリックし、API Key を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/db7a7a3c-9c0a-48e9-9aac-bb19fa4f6a85.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/d014e55f-5cb9-93a0-e1e5-3dc2fd31d58d.png)

###### Verify サービスの設定
Account Dashboard > Verify > Services から「Create new」で作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/efc94f11-936e-840a-788d-f9d86c436f24.png)
![スクリーンショット 2024-10-02 115513.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/dd95ecf2-2347-f65b-2965-0a5fad85717f.png)

#### Keycloak の設定
`[KEYCLOAK_HOME]/providers`に作成した jar ファイルを配置し、再起動することでデプロイされます。
先述の通り、ホットデプロイは廃止されています。
その他の設定については前の章で触れた内容を流用出来ますので、省略します。

# 第9章 Keycloak の非機能面の考慮ポイント
## HA 構成
構成は書籍と同じです。
なお、Quarkus 版では「ドメインクラスターモード」は廃止されました。
#### ロードバランサーの構築
今回は nginx を使用して構築しました。Keycloak サーバーとは別マシンに構築しています。
インストール→`systemctl enable nginx`によって自動起動にし、`/etc/nginx/conf.d/nginx.conf`を開いて以下の内容を書き込みます。
設定するすべてのホスト名・FQDN が名前解決できることを確認してください。
``` nginx.conf
upstream backend {
        server keycloak-test.keycloak.test:8280;
        server keycloak-test.keycloak.test:8380;
}

server {
        listen 8080;
        server_name keycloak-lb;

        location / {
                proxy_pass http://backend;
                proxy_http_version 1.1;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $remote_addr;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port $server_port;
    }
}
```
`nginx -t`で構文を確かめ、問題なければ`systemctl restart nginx`で nginx を再起動します。

#### 外部 DB の設定 
今回は PostgreSQL を外部 DB にする設定を検証しました。
###### PostgreSQLのセットアップ
`sudo apt install postgresql`を実行してインストールし、`/etc/postgresql/(バージョン)/main/postgresql.conf`をエディタで開いて、以下のようにアンコメントします。
```diff
- #listen_address = 'localhost'
+ listen_address = 'localhost'
```
また、`/etc/postgresql/(バージョン)/main/pg_hba.conf`に以下を追加します。
```diff 
+ host keycloak keycloak 127.0.0.1/32 scram-ha-256
```
(`pg_hba.conf`の詳細については[こちら](https://www.postgresql.jp/document/16/html/auth-pg-hba-conf.html))
`sudo systemctl restart postgresql`を実行し PostgreSQL を再起動します。
`sudo -u postgres psql`でインストール時に設定された`postgres`ユーザーで psql コマンドを実行し、以下のクエリを実行します。
```
create database keycloak;
create role keycloak with login password 'keycloak';
grant all on database keycloak to keycloak;
alter database keycloak owner to keycloak;
```
最下段、データベース`keycloak`のオーナーを`keycloak`に変更していますが、これは PostgreSQL 15 以後においてデータベースオーナー以外の`public`への書き込みが制限されている仕様を回避するためです。
[PostgreSQL 15 リリースノート](https://www.postgresql.org/docs/15/release-15.html)
Keycloak では、デフォルトで`public`スキーマにテーブルを作成するため、作成先のスキーマを変更するか、このように`public`スキーマへの書き込みができる状態にする必要があります。
###### Keycloakの設定
Quarkus 版 Keycloak では、Oracle を除いて[Keycloak がサポートする DB ](https://www.keycloak.org/server/db)の JDBC ドライバーがすでに組み込まれています。
そのため、起動オプション、環境変数、`keycloak.conf`のいずれかに必要な設定を書き込めば外部 DB を使うことができます。
今回は`keycloak.conf`に設定しました。
``` keycloak.conf
db=postgres
db-username=keycloak
db-password=keycloak
db-url=jdbc:postgresql://localhost:5432/keycloak
```
これで外部 DB に関する設定は完了です。

#### Keycloak の設定
以下の定義を行います。
``` keycloak.conf
proxy-headers=xforwarded 
hostname=keycloak-lb.keycloak.test
cache=ispn
cache-config-file=cache-jdbc-ping.xml
```
上から`X-Forwarded-*`ヘッダーを認識する設定、生成する識別子を一意にする設定、分散キャッシュを有効する設定、分散キャッシュに関する詳細設定を定義したファイルを読ませる設定です。
次に、`[KEYCLOAK_HOME]/conf/cache-ispn.xml`をコピーし、`cache-jdbc-ping.xml`にリネームします。
このファイルに以下を追加します。
```diff
<infinispan
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:infinispan:config:15.0 http://www.infinispan.org/schemas/infinispan-config-15.0.xsd"
        xmlns="urn:infinispan:config:15.0">
        
+  <jgroups>
+    <stack name="jdbc-ping-tcp" extends="tcp">
+      <JDBC_PING connection_driver="org.postgresql.Driver"
+                 connection_username="keycloak" connection_password="keycloak"
+                connection_url="jdbc:postgresql://localhost:5432/keycloak"
+                 initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, cluster_name varchar(200) NOT NULL, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name));"
+                 info_writer_sleep_time="500"
+                 remove_all_data_on_view_change="true"
+                 stack.combine="REPLACE"
+                 stack.position="MPING" />
+    </stack>
+  </jgroups>

    <cache-container name="keycloak">
+        <transport lock-timeout="60000" stack="jdbc-ping-tcp"/>
-        <transport lock-timeout="60000" stack="udp"/>
（以下略）
```
これによって、クラスター間通信に JDBC_PING を利用できるようになります。
参考: https://gist.github.com/xgp/768eea11f92806b9c83f95902f7f8f80
なお、分散キャッシュの`owners`の数について書籍では変更が求められますが、Quarkus版のデフォルトは`owners=2`なので、この設定は不要です。
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/2f657d75-f93e-9f1e-487e-fc45dd309a71.png)

#### 2台のインスタンスの起動
[KEYCLOAK_HOME]ディレクトリをコピーして、それぞれ次のように`keycloak.conf`を変更します。

``` keycloak.conf (インスタンス1用）
http-port=8280
```
``` keycloak.conf (インスタンス2用）
http-port=8380
http-management-port=9100
```
Keycloak 25.0.0 より、[Management Interfaceのポートが標準のポートとは分離された](https://www.keycloak.org/docs/latest/release_notes/index.html#management-port-for-metrics-and-health-endpoints)ため、インスタンス2ではデフォルトの`9000`から`9100`にしてインスタンス1と被らないようにする必要があります。
この状態で Keycloak を起動し、検証を進めましょう。

## HTTPS の設定
今回は「ブラウザーとKeycloakが直接通信する方式」で、自己署名証明書を使用して検証しました。
#### キーストアファイルの設定
`keycloak.conf`に以下の設定を追加します。
```
https-key-store-file=(キーストアファイルのパス)
https-key-store-password=(キーストアのパスワード)
```
#### HTTPリスナの無効化
`keycloak.conf`に以下を追加すればできるはずでしたが、開発モードでは無効になりませんでした。（無効化できない？）
本番モードではデフォルトで`false`に設定されているため、`hostname`や外部 DB の設定など、本番モードでの起動に必要な設定を記述して、`start`オプションで起動して検証するのもよいでしょう。
```
http-enabled=false
``` 
#### Require SSL の設定
書籍ではRealm settings > Login タブから設定することになっていますが、25.0.6ではRealm settings > General タブから設定します。
![image (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/1c44444c-87c7-6b81-963f-3e6cc89c6f92.png)

## 管理コンソールのアクセス制御
Quarkus 版ではこの機能は削除されています。

## ログとイベント
#### サーバーログ
`keycloak.conf`に設定します。
デフォルトではサーバーログはコンソールにのみ出力するので、ファイルにも出力したい場合は`log=console,file`を追加します。
この時、ログファイルは`[KEYCLOAK_HOME]/data/log/keycloak.log`になります。
出力先を変える場合は`log-file=(パス)`を追加します。
ログレベルの変更は`log-level=(レベル)`で設定します。
特定のパッケージのみレベルを変更する場合は、`log-level=(全体のレベル),(パッケージ名):(パッケージに適用するレベル)`を設定します。
例: `log-level=INFO,org.hibernate.SQL:DEBUG`の場合
```
test@keycloak-test:/keycloak/keycloak-25.0.6$ ./bin/kc.sh start
2024-10-03 14:07:17,617 WARN  [io.quarkus.runtime.configuration.DeprecatedRuntimePropertiesRecorder] (main) The 'quarkus.log.console.color' config property is deprecated and should not be used anymore
2024-10-03 14:07:18,995 INFO  [org.infinispan.CONTAINER] (Thread-5) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2024-10-03 14:07:19,715 WARN  [org.jgroups.stack.Configurator] (Thread-5) JGRP000014: ThreadPool.thread_dumps_threshold has been deprecated: ignored
2024-10-03 14:07:19,801 INFO  [org.infinispan.CLUSTER] (Thread-5) ISPN000078: Starting JGroups channel `ISPN` with stack `udp`
2024-10-03 14:07:19,848 INFO  [org.jgroups.JChannel] (Thread-5) local_addr: f87cd34c-8ecf-47e5-a33a-e18736401e1a, name: keycloak-test-2489
2024-10-03 14:07:19,871 WARN  [org.jgroups.protocols.UDP] (Thread-5) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-10-03 14:07:19,873 WARN  [org.jgroups.protocols.UDP] (Thread-5) JGRP000015: the receive buffer of socket MulticastSocket was set to 20MB, but the OS only allocated 212.99KB
2024-10-03 14:07:19,874 WARN  [org.jgroups.protocols.UDP] (Thread-5) JGRP000015: the send buffer of socket MulticastSocket was set to 1MB, but the OS only allocated 212.99KB
2024-10-03 14:07:19,875 WARN  [org.jgroups.protocols.UDP] (Thread-5) JGRP000015: the receive buffer of socket MulticastSocket was set to 25MB, but the OS only allocated 212.99KB
2024-10-03 14:07:19,922 INFO  [org.jgroups.protocols.FD_SOCK2] (Thread-5) server listening on *.31627
2024-10-03 14:07:20,526 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
2024-10-03 14:07:21,957 INFO  [org.jgroups.protocols.pbcast.GMS] (Thread-5) keycloak-test-2489: no members discovered after 2014 ms: creating cluster as coordinator
2024-10-03 14:07:21,982 INFO  [org.infinispan.CLUSTER] (Thread-5) ISPN000094: Received new cluster view for channel ISPN: [keycloak-test-2489|0] (1) [keycloak-test-2489]
2024-10-03 14:07:22,343 INFO  [org.infinispan.CLUSTER] (Thread-5) ISPN000079: Channel `ISPN` local address is `keycloak-test-2489`, physical addresses are `[192.168.181.50:47163]`
2024-10-03 14:07:23,054 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: keycloak-test-2489, Site name: null
2024-10-03 14:07:23,950 DEBUG [org.hibernate.SQL] (main) select re1_0.ID from REALM re1_0 where re1_0.NAME=?
2024-10-03 14:07:24,132 DEBUG [org.hibernate.SQL] (main) select re1_0.ID from REALM re1_0 where re1_0.NAME=?
2024-10-03 14:07:24,191 DEBUG [org.hibernate.SQL] (main) select re1_0.ID,re1_0.ACCESS_CODE_LIFESPAN,re1_0.LOGIN_LIFESPAN,re1_0.USER_ACTION_LIFESPAN,re1_0.ACCESS_TOKEN_LIFESPAN,re1_0.ACCESS_TOKEN_LIFE_IMPLICIT,re1_0.ACCOUNT_THEME,re1_0.ADMIN_EVENTS_DETAILS_ENABLED,re1_0.ADMIN_EVENTS_ENABLED,re1_0.ADMIN_THEME,re1_0.ALLOW_USER_MANAGED_ACCESS,re1_0.BROWSER_FLOW,re1_0.CLIENT_AUTH_FLOW,re1_0.DEFAULT_LOCALE,re1_0.DEFAULT_ROLE,re1_0.DIRECT_GRANT_FLOW,re1_0.DOCKER_AUTH_FLOW,re1_0.DUPLICATE_EMAILS_ALLOWED,re1_0.EDIT_USERNAME_ALLOWED,re1_0.EMAIL_THEME,re1_0.ENABLED,re1_0.EVENTS_ENABLED,re1_0.EVENTS_EXPIRATION,re1_0.INTERNATIONALIZATION_ENABLED,re1_0.LOGIN_THEME,re1_0.LOGIN_WITH_EMAIL_ALLOWED,re1_0.MASTER_ADMIN_CLIENT,re1_0.NAME,re1_0.NOT_BEFORE,re1_0.OFFLINE_SESSION_IDLE_TIMEOUT,re1_0.OTP_POLICY_ALG,re1_0.OTP_POLICY_DIGITS,re1_0.OTP_POLICY_COUNTER,re1_0.OTP_POLICY_WINDOW,re1_0.OTP_POLICY_PERIOD,re1_0.OTP_POLICY_TYPE,re1_0.PASSWORD_POLICY,re1_0.REFRESH_TOKEN_MAX_REUSE,re1_0.REGISTRATION_ALLOWED,re1_0.REG_EMAIL_AS_USERNAME,re1_0.REGISTRATION_FLOW,re1_0.REMEMBER_ME,re1_0.RESET_CREDENTIALS_FLOW,re1_0.RESET_PASSWORD_ALLOWED,re1_0.REVOKE_REFRESH_TOKEN,re1_0.SSL_REQUIRED,re1_0.SSO_IDLE_TIMEOUT,re1_0.SSO_IDLE_TIMEOUT_REMEMBER_ME,re1_0.SSO_MAX_LIFESPAN,re1_0.SSO_MAX_LIFESPAN_REMEMBER_ME,re1_0.VERIFY_EMAIL,a1_0.REALM_ID,a1_0.NAME,a1_0.VALUE from REALM re1_0 left join REALM_ATTRIBUTE a1_0 on re1_0.ID=a1_0.REALM_ID where re1_0.ID=?
```
`org.hibernate.SQL`が出力するログのみが`DEBUG`、他が`INFO`でロギングされていることがわかります。
その他の設定については[公式ドキュメント](https://www.keycloak.org/server/logging)を参照してください。
ログローテートや保存する世代数の設定についてはQuarkusの機能を利用する必要があります。
詳細は[Quarkusのリファレンス](https://ja.quarkus.io/guides/logging#loggingConfigurationReference)を参照してください。
#### アクセスログ
Keycloak のデフォルトには存在しないため、Quarkus の設定を利用します。
`[KEYCLOAK_HOME]/conf/quarkus.properties`を作成し、以下を追加します。
```
quarkus.http.access-log.enabled=true
```
これにより、アクセスログが有効化されます。
![image (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/6f32fbca-b1cb-6805-c554-69a706ab5179.png)

デフォルトではコンソールにのみ出力されます。ファイルにも出力させたい場合は以下も追加します。
```
quarkus.http.access-log.log-to-file=true
```
デフォルトでは`Quarkus.log`が作業ディレクトリに出力されます。
![image (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/d65701c6-8eea-c040-fe04-a0c7ab0bf83b.png)
その他の設定については[QuarkusのHTTPリファレンス](https://ja.quarkus.io/guides/http-reference#configuring-http-access-logs)を参照してください。
#### 監査ログ
書籍では「Events」から設定していましたが、25.0.6ではRealm settings > Events タブから設定します。
###### ログインイベント
Login Events Settings は User events settings に名称が変わっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/67ff57cb-65f2-c2a9-2aa4-c948ab8863eb.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/366b04e1-624e-7d92-8899-39dea5740034.png)

User events settingsで保存する情報を変更したい場合はケバブメニューから「Remove」か、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/30359763-5909-5b5e-bea3-3344ec831fc5.png)
「Add saved types」から追加するようにしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3900965/c9b415f5-d27c-493d-4d9d-e1b49a4b698f.png)
###### 監査イベント
監査イベントをファイルに出力したい場合は`org.keycloak.events`などを指定して、`keycloak.conf`に`log-level=`で定義しましょう。

# 最後に
以上が Keycloak 25.0.6 における、各検証での差異・注意点になります。
これを読んだ方が少しでもスムーズに検証を行えていることを祈ります。
Quarkus 版でのドキュメントが少なく、公式リファレンス以外から取得した情報も含んでおりますので、不正確な情報が含まれている可能性があります。
記事の内容に間違いがありましたらコメントでご指摘いただければ幸いです。
最後までお読みいただき、ありがとうございました。