KeycloakのHostname SPI

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

# Hostname SPI を利用するシーン

今回は`Keycloak 4.3.0.Final`から登場した Hostname SPI という機能について説明します。Keycloak は自身の URL をリクエストの内容をもとに生成しています。これらの値は、`/.well-konwn/openid-configuration`の値やアクセストークン、ID トークンの`iss`などの値に利用されています。

デフォルトの設定では、リクエスト時のホスト名とスキーマの値を利用しますが、ロードバランサーなどを挟む場合には`http-listener`の設定に`proxy-address-forwarding="true"`というアトリビュートを指定します。この設定を行うと、`X-Fowrarded-Proto`, `X-Frowarded-For`といったヘッダの値をもとに、自身の URL を生成するようになります([参考](https://www.keycloak.org/docs/latest/server_installation/index.html#_setting-up-a-load-balancer-or-proxy))。なお、Keycloak のコンテナイメージを利用する際、環境変数`PROXY_ADDRESS_FORWARDING`に`true`を指定すると、同じように設定を行うことができます。

通常はこれらの設定で十分なのですが、下記のようなケースで問題が出てきます。

1. ロードバランサーの内側では内部的な Host 名でアクセスしたい
1. Keycloak とアプリケーションが同一サーバで動いているため、localhost でアクセスしたい
1. Kubernetes 等を利用しているので、クラスタ内部の Service 経由(~.svc.cluster.local など)でアクセスしたい

上記のような場合、ネットワークの内部と外部で Keycloak の URL が異なってしまいます。

![issの値が異なる場合](https://qiita-image-store.s3.amazonaws.com/0/273221/642cbeba-6937-b0d9-2c29-926d59cc9a07.png)

この様な状態だと、例えば ID トークン、アクセストークンのバリデーションを行った際に`iss`の値が異なるためエラーとなる、Token Introspection を行った際も無効なトークンとして扱われてしまうなどといった問題が発生します。
「アクセストークンが正しく発行できているのにリクエストがエラーになる」となっていると、大抵この問題を踏んでいたりします。

Hostname SPI を利用することで、Keycloak の URL を柔軟に変更することができるようになり、これらの問題を回避することができるようになります。

# Hostname SPI の概要

Hostname SPI は名前の通り、Keycloak の Hostname を設定するための`Service Provider Interface`(`SPI`: Keycloak をカスタマイズするために提供されているインターフェース)です。`Keycloak 4.3.0.Final`では、このインターフェースを実装した`RequestHostnameProvider`および`FixedHostnameProvider`が提供されています。

`RequestHostnameProvider`はこれまで同様、リクエストの値をもとにして Keycloak の URL を生成します。一方`FixedHostnameProvider`は、常にプロパティーで設定した値をもとに Keycloak の URL を生成します。`FixedHostnameProvider`に指定できるプロパティーとしては、`hostname`,`httpPort`,`httpsPort`があり、さらに`4.6.0.Final`では`alwaysHttps`というプロパティーも追加されました。

また、`HostnameProvider`というインターフェースを実装すれば、独自にカスタマイズすることが可能です。

# Hostname SPI の設定方法と確認方法

`RequestHostnameProvider`, `FixedHostnameProvider`のどちらを利用するか、および`FixedHostnameProvider`のプロパティーは Keycloak の設定ファイル(`standalone.xml`, `standalone-ha.xml`など) で設定します。

今回は`FixedHostnameProvider`を利用するため、下記のように設定しました。

```xml:standalone.xml
    <spi name="hostname">
        <default-provider>fixed</default-provider>
        <provider name="fixed" enabled="true">
            <properties>
                <property name="hostname" value="keycloak.external.net"/>
                <property name="httpPort" value="80"/>
                <property name="httpsPort" value="443"/>
                <property name="alwaysHttps" value="true"/>
            </properties>
        </provider>
    </spi>
```

この設定を行った状態で Keycloak を起動し、`http://localhost:8080/auth/realms/master/.well-known/openid-configuration`にアクセスすると、下記のように`issuer`や各 Endpoint の URL が Hostname SPI で指定した値になります。

```json:/.well-known/openid-configuration
{
  "issuer": "https://keycloak.external.net/auth/realms/master",
  "authorization_endpoint": "https://keycloak.external.net/auth/realms/master/protocol/openid-connect/auth",
  "token_endpoint": "https://keycloak.external.net/auth/realms/master/protocol/openid-connect/token",
  "token_introspection_endpoint": "https://keycloak.external.net/auth/realms/master/protocol/openid-connect/token/introspect",
  ...
}
```

# まとめ

この様に Hostname SPI を利用すると、Keycloak の URL を固定化するなど柔軟に設定を行うことができるようになります。`iss`の値が違う、などの問題が出た際にはこの機能を利用してみてください。