Keycloak に独自の REST API を追加する

## はじめに

Keycloak には拡張機能を配置することで独自の REST API を追加できる仕組みがあります。
公式リポジトリにあるサンプルで拡張機能のビルド・デプロイの一連の流れを試したので、その手順を紹介します。

## 環境

Keycloak 15.0.2

```zsh:terminal
% mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /usr/local/Cellar/maven/3.8.4/libexec
Java version: 17.0.1, vendor: Homebrew, runtime: /usr/local/Cellar/openjdk/17.0.1_1/libexec/openjdk.jdk/Contents/Home
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "11.2.3", arch: "x86_64", family: "mac"
```

## 使用するサンプル

使用するサンプルは [Example Realm REST Resource provider](https://github.com/keycloak/keycloak/tree/af3b573d196af882dfb25cdccb98361746e85481/examples/providers/rest) です。
このサンプルは Keycloak に REST エンドポイント `GET /auth/realms/{realm}/hello` を追加する拡張機能を作成するものです。
言語は Java によって書かれており、Maven を使用してビルドします。
追加されたエンドポイントにリクエストを送信すると `Hello Keycloak` というレスポンスを返します。

## 手順

### 1. Keycloak を起動する

拡張機能のデプロイ先とする Keycloak を Docker で起動しておく。

```zsh:terminal
% docker run -d --name keycloak -p 8180:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:15.0.2
```

追加する予定のエンドポイントにリクエストを送信してみて、レスポンスが `"HTTP 404 Not Found"` になることを確認する。

```zsh:terminal
% curl -X GET "http://localhost:8180/auth/realms/master/hello"
{"error":"HTTP 404 Not Found"}
```

### 2. 拡張機能のソースを用意する

Keycloak のリポジトリをクローンする。

```zsh:terminal
% git clone https://github.com/keycloak/keycloak.git
```

[最新リリース (執筆時点)](https://github.com/keycloak/keycloak/releases/tag/15.0.2) のタグをチェックアウトする。

```zsh:terminal
% cd keycloak
% git checkout -b v15.0.2 refs/tags/15.0.2
```

拡張機能のサンプルのディレクトリに移動する。

```zsh:terminal
% cd examples/providers/rest
```

### 3. 拡張機能をビルドする

ソースをビルドしてプロバイダー JAR ファイルを作成します。
2 通りの方法があるので好きな方を選んでください。

#### 3.a `mvn` コマンドを使用する (おすすめ)

`mvn` コマンドで JAR ファイルをビルドする。

```zsh:terminal
% mvn clean install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
```

`target` ディレクトリに `hello-rest-example.jar` ファイルが作成されたことを確認する。

```zsh:terminal
% ls target
hello-rest-example.jar
```

#### 3.b InteliJ IDEA を使用する

InteliJ IDEA でプロジェクトを開く

```zsh:terminal
~/keycloak/examples/providers/rest% idea .
```

まず、メニューバーの `File > Project Structure...` をクリックする。
Project Structure ウィンドウが表示されたら、サイドバーの `Project Setting > Artifacts` を選択する。
\+ ボタンをクリックして `JAR > From modules with dependencies...` を選択する。
Create JAR from Modules ウィンドウが表示されたら extract to target JAR のラジオボタンが選択されていることを確認する。
OK をクリックする。
次に、メニューバーの `Build > Build Artifacts...` をクリックする。
Action リストの Build を選択する。
すると、`out` ディレクトリ以下に `hello-rest-example.jar` ファイルが作成される。

```zsh:terminal
% ls out/artifacts/rest_jar
hello-rest-example.jar
```

### 4. 拡張機能をデプロイする

プロバイダー JAR ファイルを Keycloak にデプロイします。
デプロイも 2 通りの方法があるので好きな方を選んでください。
ホットデプロイができるので Keycloak Deployer を使用するのがおすすめです。

#### 4.a Keycloak Deployer を使用する (おすすめ)

プロバイダー JAR ファイルを `/opt/jboss/keycloak/standalone/deployments` ディレクトリにコピーする。

```zsh:terminal
% docker cp target/hello-rest-example.jar keycloak:/opt/jboss/keycloak/standalone/deployments/.
```

拡張機能のデプロイが成功したか否かを確認する。
Keycloak のログを表示して `Deployed "hello-rest-example.jar"` と出力されていたら成功。

```zsh:terminal
% docker logs -f keycloak
...
XX:XX:XX,XXX INFO  [org.jboss.as.repository] (DeploymentScanner-threads - 2) WFLYDR0001: Content added at location /opt/jboss/keycloak/standalone/data/content/e6/fe4edb414c9c15d4da06474575dbe8974803ab/content
XX:XX:XX,XXX INFO  [org.jboss.as.server.deployment] (MSC service thread 1-7) WFLYSRV0027: Starting deployment of "hello-rest-example.jar" (runtime-name: "hello-rest-example.jar")
XX:XX:XX,XXX INFO  [org.keycloak.subsystem.server.extension.KeycloakProviderDeploymentProcessor] (MSC service thread 1-5) Deploying Keycloak provider: hello-rest-example.jar
XX:XX:XX,XXX WARN  [org.keycloak.services] (MSC service thread 1-5) KC-SERVICES0047: hello (org.keycloak.examples.rest.HelloResourceProviderFactory) is implementing the internal SPI realm-restapi-extension. This SPI is internal and may change without notice
XX:XX:XX,XXX INFO  [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0010: Deployed "hello-rest-example.jar" (runtime-name : "hello-rest-example.jar")
```

また、デプロイが成功した場合は `/opt/jboss/keycloak/standalone/deployments` ディレクトリに `.deployed` という拡張子のファイルが作成される。
失敗した場合は `.failed` という拡張子のファイルが作成される。

```zsh:terminal
% docker exec -it -w /opt/jboss/keycloak keycloak ls standalone/deployments
hello-rest-example.jar  hello-rest-example.jar.deployed  README.txt
```

#### 4.b Modules を使用する

プロバイダー JAR ファイルを Keycloak コンテナの適当な場所にコピーする。

```zsh:terminal
% docker cp target/hello-rest-example.jar keycloak:/tmp/.
```

Keycloak コンテナの中で `jboss-cli.sh` を実行する。
`--resources` オプションには先ほどコピーしたプロバイダー JAR ファイルを指定すること。

```shell:terminal
% docker exec -it -w /opt/jboss/keycloak keycloak /bin/bash
$ ./bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.hello-rest-example --resources=/tmp/hello-rest-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-server-spi,org.keycloak.keycloak-server-spi-private,javax.ws.rs.api"
```

`/opt/jboss/keycloak/standalone/configuration` ディレクトリの `standalone.xml` ファイルと `standalone-ha.xml` ファイルに provider タグを追加する。

```bash:terminal
$ sed -ie '432i <provider>module:org.keycloak.examples.hello-rest-example</provider>' standalone/configuration/standalone.xml
$ sed -ie '490i <provider>module:org.keycloak.examples.hello-rest-example</provider>' standalone/configuration/standalone-ha.xml
```

```diff:standalone(-ha).xml
 <subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
     <web-context>auth</web-context>
     <providers>
         <provider>
             classpath:${jboss.home.dir}/providers/*
         </provider>
+        <provider>module:org.keycloak.examples.hello-rest-example</provider>
     </providers>
     <master-realm-name>master</master-realm-name>
     <scheduled-task-interval>900</scheduled-task-interval>
     <theme>
         <staticMaxAge>2592000</staticMaxAge>
         <cacheThemes>true</cacheThemes>
```

Keycloak を再起動する。

```shell:terminal
$ exit
% docker restart keycloak
```

### 5. 動作を確認する

追加されたエンドポイントにリクエストを送信する。

```zsh:terminal
% curl -X GET "http://localhost:8180/auth/realms/master/hello"
Hello Keycloak
```

`Hello Keycloak` と出力されたら成功です。

## おわりに

Keycloak に独自の REST API を追加できることが確認できました。

## 参考文献

1. [Example Realm REST Resource provider - keycloak/keycloak](https://github.com/keycloak/keycloak/tree/af3b573d196af882dfb25cdccb98361746e85481/examples/providers/rest)
1. [カスタムRESTエンドポイントを追加 - Server Developer Guide](https://keycloak-documentation.openstandia.jp/13.0.1/ja_JP/server_development/index.html#_extensions_rest)
1. [プロバイダー実装の登録 - Server Developer Guide](https://keycloak-documentation.openstandia.jp/13.0.1/ja_JP/server_development/index.html#プロバイダー実装の登録)
1. [独自カスタマイズ - Keycloakのカスタマイズポイントを整理してみる](https://qiita.com/yoonis/items/298d580f7e93e56ddf6a#独自カスタマイズ)
1. [Keycloak Custom Rest Api (Search by user attribute - Keycloak)](https://dev.to/silentrobi/keycloak-custom-rest-api-search-by-user-attribute-keycloak-3a8c)
1. [Keycloak : How to create custom rest api](https://gaurav-wadhone.blogspot.com/2020/09/keycloak-how-to-create-custom-rest-api.html)
1. [アプリケーションを JAR にパッケージ化する - IntelliJ IDEA を使用してアプリケーションをコンパイルおよびビルドする](https://pleiades.io/help/idea/compiling-applications.html#package_into_jar)