Keycloakの新機能：Keycloak Gatekeeperを試してみた

**※2018/12/21追記**
Keycloakのバージョン4.8.0.Finalで、『[Keycloakにaudクレームの設定を追加する](#keycloak%E3%81%ABaud%E3%82%AF%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)』の章に記した問題がドキュメントに[Known Issues](https://www.keycloak.org/docs/latest/securing_apps/index.html#known-issues)として追加されました。

---

## はじめに

[UL Systems Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ulsystems)の8日目です。

### なぜこの記事を書いたか

[Keycloak Gatekeeper](https://www.keycloak.org/)は、[Keycloak](https://www.keycloak.org/)のアダプターモジュールとして、2018/11/14リリースの Keycloak 4.6.0.Final で新たに追加されました（[リリースノート](https://www.keycloak.org/docs/latest/release_notes/index.html)）。
まだWeb上に情報が少ないので、今回これを試してみることにしました。

### Keycloak Gatekeeperとは

Keycloak GatekeeperはGo言語で実装されたアプリケーションで、APIの手前で[OpenID Connect](https://openid.net/connect/)に対応する認可リバースプロキシとして動作します。

一般的に、API側で共通の処理を行いたいケースでは、個々のAPIでそれぞれ処理する方式よりも、APIの手前で共通処理を集約する方式が主流となっています。
この方式はマイクロサービスのデザインパターンの一つとして『[API Gatewayパターン](https://microservices.io/patterns/apigateway.html)』と呼ばれ、個々のAPIが共通的に必要とする処理を手前のリバースプロキシで集約して行うことにより、APIの呼び出しや保護、管理をより容易にできるというメリットがあります。

Keycloak GatekeeperはAPI Gatewayパターンに則り、バックエンドのAPIに代わってアクセストークンの検証処理を行うことでOpenID ConnectのResource Serverの役割を提供します。

なおKeycloak Gatekeeperは、[Keycloak Security Proxy](https://keycloak-documentation.openstandia.jp/master/ja_JP/server_installation/index.html#_proxy)という同様の役割を持つプロキシがDeprecatedとなり、後継として新たに作られたものです。
（参考：『[Keycloakでリバプロ型構成を組んでみる（Security Proxy編）](https://qiita.com/yagiaoskywalker/items/ba423a55ed8389c29119)』）

## 試してみる

### 確かめたいこと

今回は、Keycloak Gatekeeperを使うことで、バックエンドのAPIに代わって認可制御を行い、アクセストークンが表す認可情報がバックエンドのAPIに渡されることを確かめます。

### 今回の構成

手軽さを重視して、WindowsのローカルPC上に全ての構成を準備することにします。

![kcgk_model.png](https://qiita-image-store.s3.amazonaws.com/0/321207/5ad6fcb3-d4c4-abb3-02dc-0525a64e9fb5.png)

|役割|アプリケーション|
|----|--------------|
|認証認可サーバー|Keycloak Server 4.7.0.Final|
|認可リバースプロキシ|Keycloak Gatekeeper 4.7.0.Final|
|認証クライアント|Keycloak Quickstarts (app-jee-html5) 4.7.0.Final|
|バックエンドAPI/ネットワークキャプチャ|Fiddler Web Debugger v5.0.20182.28034|
|ブラウザ|Google Chrome 71.0.3578.80|

Keycloakシリーズは執筆時点の最新バージョンである4.7.0.Finalを使います。

#### 認証認可サーバー

OpenID ConnectのIdentity Providerとして、Keycloak Server（以下、Keycloak）を使います。
KeycloakはアプリケーションサーバーのWildFlyに同梱された形で配布されています。

#### 認可リバースプロキシ

今回のターゲットである、Keycloak Gatekeeperを使います。
なおKeycloak Gatekeeperは、Keycloak以外のOpenID Connect Identity Providerとも[お友達になれる](https://github.com/keycloak/keycloak-gatekeeper#keycloak-gatekeeper)そうです。

#### 認証クライアント

OpenID Connectの認証クライアント（Relying Party）を一から実装するのは手間がかかるので、今回はKeycloakのデモアプリケーションである [Keycloak Quickstarts](https://github.com/keycloak/keycloak-quickstarts) の中の『app-jee-html5』という認証クライアントを使うことにします。
これはKeycloakのJavaScriptアダプターである[keycloak.js](https://keycloak-documentation.openstandia.jp/master/ja_JP/securing_apps/index.html#_javascript_adapter)を利用したHTML5+JavaScriptのアプリケーションで、Keycloakが同梱されているWildFlyにWARとしてデプロイすることによって動作します。

#### ネットワークキャプチャ

HTTPリクエストとレスポンスを可視化するネットワークキャプチャとして、Fiddler Web Debugger（以下、Fiddler）を使います。

#### バックエンドAPI

FiddlerのAutoResponder機能を使って、リクエストに応じた固定のレスポンスを設定することにより、疑似的にバックエンドAPIの振る舞いをさせます。

### 環境構築

以下の環境を前提とします。

- Java JDK 8
- Apache Maven 3.3.1以上

#### Keycloakのダウンロード、起動

[Keycloakのダウンロードページ](https://www.keycloak.org/downloads.html)で、ServerのZIPをダウンロード、解凍します。
解凍したフォルダ直下のbin/standalone.bat を起動します。

```console
C:\example\keycloak-4.7.0.Final\bin>standalone.bat
```

起動後に http://localhost:8080/auth にアクセスし、『Administration Console』（以下、管理画面）から管理ユーザを作成してログイン後、以下の手順で日本語化しておきます。

1. Realm Settings で masterレルムの『Themes』タブを開く
1. 『Internationalization Enabled』をONにする。
1. 『Default Locale』で『ja』を選択し、saveする。
1. 右上の管理ユーザを選択し、『Sign out』で一度サインアウトする。

#### Keycloakの設定

今回の構成のために、Keycloakの管理画面で以下の設定を行います。

|対象|項目|設定内容|
|-----|---|---------|
|レルム|レルム名|example|
|ロール(1)|ロール名|USER|
|ロール(2)|ロール名|ADMIN|
|クライアント(1)|クライアントID|gatekeeper|
||アクセスタイプ|confidential|
||有効なリダイレクト URI|http://localhost:3000/oauth/callback|
|クライアント(2)|クライアントID|app-html5|
||アクセスタイプ|public|
||有効なリダイレクト URI|http://localhost:8080/app-html5|
|ユーザー(1)|ユーザー名|staff|
||新しいパスワード|staffpwd|
||パスワード『一時的』|OFF|
||アサイン済みロール|USER|
|ユーザー(2)|ユーザー名|admin|
||新しいパスワード|adminpwd|
||パスワード『一時的』|OFF|
||アサイン済みロール|USER, ADMIN|

app-jee-html5のクライアント名が『app-html5』となっていることに注意。

#### Keycloakにaudクレームの設定を追加する

Keycloak Gatekeeperはアクセストークンの検証時に、JWT内の"aud"クレームと自身のクライアントIDが一致していることを確認する処理があり、これがしばしば大きな問題になります。（というかハマりました。。）
本質的には、アクセストークンはIDトークンと違って特定のクライアントを受け取り手として発行するものではないので、Keycloakが"aud"クレームを含めないようにするか、Keycloak Gatekeeperがこの確認を行わないようにしてくれるのが望ましいはずなのですが、そうなっていません。
（Keycloakのissueで[議論](https://issues.jboss.org/browse/KEYCLOAK-8954)されていますが、執筆時点では結論はまだのようです）

したがって、この問題を回避するために、アクセストークンの"aud"クレームにKeycloak GatekeeperのクライアントIDをセットする設定をKeycloakに対して追加します。

1. Keycloakの管理画面で、app-html5クライアントの『マッパー』タブで、『作成』する。
1. 名前を付けて、マッパータイプで『Audience』を選択する。
1. 『Included Client Audience』で『gatekeeper』を選択する。
1. 『アクセストークンに追加』のみONにして、『保存』する。

設定後はこのようになります。

![kcgk_aud_mapper.png](https://qiita-image-store.s3.amazonaws.com/0/321207/1dabea08-7db0-ef4e-025b-ca7323348335.png)

#### Keycloak Gatekeeperのダウンロード

上記のKeycloakのダウンロードページから GatekeeperのWindows版をダウンロードします。
ダウンロードしたファイル keycloak-gatekeeper-windows-amd64.tar.gz を解凍して、実行ファイルである keycloak-gatekeeper.exe を配置します。
この配置したフォルダを、Keycloak Gatekeeperの実行フォルダとします。

#### Keycloak Gatekeeperの設定

Keycloak Gatekeeperに、APIプロキシとして必要な設定を行います。

まず、Keycloak Gatekeeperの実行フォルダで、コマンドラインからヘルプを確認してみます。

```console
C:\example\keycloak-gatekeeper>keycloak-gatekeeper.exe help

NAME:
   keycloak-gatekeeper - is a proxy using the keycloak service for auth and authorization

USAGE:
   keycloak-gatekeeper [options]

VERSION:
   4.7.0 (git+sha: 0c7fe8b-dirty, built: 04-12-2018)

AUTHOR:
   Rohith Jayawardene <gambol99@gmail.com>

COMMANDS:
     help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --config value                            path the a configuration file [%PROXY_CONFIG_FILE%]
   --listen value                            the interface the service should be listening on [%PROXY_LISTEN%]
   --listen-http value                       interface we should be listening [%PROXY_LISTEN_HTTP%]

（略）
```

設定は、コマンドラインの引数か、設定ファイルで与えることができます。
また、一部の設定は環境変数からも与えることができます。

今回は、Keycloak Gatekeeperの実行フォルダに、次の内容の設定ファイル`config.yaml`を新規作成します。

```yaml
listen: localhost:3000 #自身のポートを指定する
discovery-url: http://localhost:8080/auth/realms/example #KeycloakのレルムのURL
client-id: gatekeeper #KeycloakのgatekeeperクライアントID
client-secret: c405299e-5f4d-48df-b7d2-4ef0eb91864a #Keycloakのgatekeeperクライアントのシークレット
redirection-url: http://localhost:3000 #Keycloakからリダイレクトで戻るエンドポイントURL
upstream-url: http://localhost:8888 #バックエンドAPIのURL
resources:
- uri: /public
  white-listed: true #/publicは認証もロールも不要
- uri: /secured
  roles:
  - USER #/securedはUSERロールが必要
- uri: /admin
  roles:
  - ADMIN #/adminはADMINロールが必要
cors-origins:
  - '*' #CORSを全てのURLに許可する
cors-methods:
  - GET #CORSをGETメソッドに許可する
cors-headers:
  - authorization #CORSをauthorizationヘッダに許可する
no-redirects: true # 認証が必要な場合に認証認可サーバーにリダイレクトしないようにする
enable-logging: true # ログ出力設定
```

一般的なブラウザでは、HTML生成元のオリジン（プロトコル、ホスト、ポートのセット）以外にデータ通信を行う『CORS（Cross-Origin Resource Sharing）』を拒否するようになっています。
今回は認証クライアントのapp-jee-html5と認可リバースプロキシのKeycloak Gatekeeperが別オリジンのため、GETメソッドのauthorizationヘッダに対してCORSを許可する設定を入れています。
（参考：『[CORSまとめ](https://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34)』）

#### Keycloak Gatekeeperの起動

設定ができたら、設定ファイルを指定して起動します。

```console
C:\example\keycloak-gatekeeper>keycloak-gatekeeper.exe --config config.yaml

1.5442497469538095e+09  info    starting the service    {"prog": "keycloak-gatekeeper", "author": "Rohith Jayawardene", "version": "4.7.0 (git+sha: 0c7fe8b-dirty, built: 04-12-2018)"}
1.5442497469548118e+09  info    attempting to retrieve configuration discovery url      {"url": "http://localhost:8080/auth/realms/example", "timeout": "30s"}
1.5442497479725373e+09  info    successfully retrieved openid configuration from the discovery
1.5442497479755454e+09  info    enabled reverse proxy mode, upstream url        {"url": "http://localhost:8888"}
1.5442497479755454e+09  info    using session cookies only for access and refresh tokens
1.5442497479765573e+09  info    adding a default denial into the protected resources
1.5442497479765573e+09  info    protecting resource     {"resource": "uri: /public, white-listed"}
1.54424974797755e+09    info    protecting resource     {"resource": "uri: /secured, methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE, required: USER"}
1.54424974797755e+09    info    protecting resource     {"resource": "uri: /admin, methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE, required: ADMIN"}
1.544249747978556e+09   info    protecting resource     {"resource": "uri: /*, methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT,TRACE, required: authentication only"}
1.5442497479795554e+09  info    keycloak proxy service starting {"interface": "localhost:3000"}
```

#### app-jee-html5のダウンロード、インストール

Keycloak Quickstarts の [app-jee-html5](https://github.com/keycloak/keycloak-quickstarts/tree/4.7.0.Final/app-jee-html5) のページから、GitまたはZIPファイルによってダウンロードします。

次に、app-jee-html5直下のconfigフォルダに、以下の内容でkeycloak.jsonを新規作成します。

```json
{
  "realm": "example",
  "auth-server-url": "http://localhost:8080/auth",
  "resource": "app-html5",
}
```

もう一つ、app-jee-html5\src\main\webapp\app.jsの以下の行を、Keycloak Gatekeeperを指すように修正します。

```javascript
# 18行目
var serviceUrl = 'http://127.0.0.1:8080/service'
↓
var serviceUrl = 'http://localhost:3000'
```

最後にWildFly起動中に、app-jee-html5のフォルダでWildFlyへデプロイします。

```console
C:\example\keycloak-quickstarts-4.7.0.Final\app-jee-html5>mvn clean wildfly:deploy
```

#### Fiddlerの起動

[Fiddlerのダウンロードページ](https://www.telerik.com/download/fiddler)で、求められる項目を入力してWindows版のインストーラー FiddlerSetup.exe をダウンロードし、インストールを行います。
（参考：『[HTTP通信のキャプチャをとるツールFiddlerをWindowsにインストールする](https://qiita.com/taketakekaho/items/397bc6e9afa32329edd0)』）

インストール後、Fiddlerを起動します。

Fiddlerは初期状態では、起動直後からローカルPC上のネットワークキャプチャを自動的に開始しますが、今回の構成で行うリクエスト以外は不要なので、以下の手順でlocalhostをホストとするリクエストのみを表示する設定を行います。

1. Filtersタブを表示します。
1. 『Use Filters』をONにします。
1. 『Hosts』の2つ目のプルダウンから『Show only the following Hosts』を選択します。
1. 上記プルダウンの直後の入力項目に『localhost』を入力します。
1. 最後に『Actions』→『Run Filterset now』を押して設定を有効化します。

![kcgk_fiddler_filter.png](https://qiita-image-store.s3.amazonaws.com/0/321207/18d8ccee-12fd-fd1e-c1c9-1841b639baf7.png)

#### Fiddlerの設定

Fiddlerの 『AutoResponder』タブで『Add Rule』として、以下の内容のようにpublic、secured、adminの3つのAPIの設定を行います。

- リクエスト（下段の『Request URL Pattern』に入力する）

    ```http
    EXACT:http://localhost:8888/public
    ```
    ```http
    EXACT:http://localhost:8888/secured
    ```
    ```http
    EXACT:http://localhost:8888/admin
    ```

- レスポンス（『Edit Response...』で『Raw』タブに入力する）
  - このレスポンスにも、CORSを許可するAccess-Control-Allow-Originヘッダが必要です。
  - Content-Lengthにはレスポンスボディの長さを設定します。

    ```http
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    Content-Type: application/json
    Content-Length: 20
  
    {"message":"public"}
    ```
    ```http
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    Content-Type: application/json
    Content-Length: 21
  
    {"message":"secured"}
    ```
    ```http
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: *
    Content-Type: application/json
    Content-Length: 19
  
    {"message":"admin"}
    ```

最後に『Enable rules』をONにすると、AutoResponderが有効になります。

### 動作確認

Chromeでapp-jee-html5の画面 http://localhost:8080/app-html5 を操作して、動作確認を行います。

![kcgk_app_0.png](https://qiita-image-store.s3.amazonaws.com/0/321207/65b7099d-123d-ea2e-b7b1-50dea1db9c77.png)

#### 未ログイン時の動作

まず、未ログインの状態でのアクセス結果です。

- /public → アクセス成功『Message: public』

![kcgk_app_public.png](https://qiita-image-store.s3.amazonaws.com/0/321207/77027c97-e531-83d9-d995-712d72f04755.png)

- /secured → アクセス失敗『401 Unauthorized』

![kcgk_app_401.png](https://qiita-image-store.s3.amazonaws.com/0/321207/4a4ff4cb-8d7f-4c7f-508c-ad972ff790e7.png)

- /admin → アクセス失敗『401 Unauthorized』
  - /securedと同じ結果

#### staffでログイン時の動作

次に、USERロールを持っているstaffアカウントでログインした状態でのアクセス結果です。
LOGINボタンでKeycloakのログイン画面で、staff/staffpwdでログインしてから操作します。

![kcgk_app_login_staff.png](https://qiita-image-store.s3.amazonaws.com/0/321207/79364a81-2929-b157-4ad2-d79ad8eb4de8.png)

- /public → アクセス成功『Message: public』

![kcgk_app_public_account.png](https://qiita-image-store.s3.amazonaws.com/0/321207/1880e0cc-b6eb-2b7e-ed44-6df7a0b0d14d.png)

- /secured → アクセス成功『Message: secured』

![kcgk_app_secured.png](https://qiita-image-store.s3.amazonaws.com/0/321207/07858382-f758-9191-c31e-5eb49717a1fc.png)

ここでFiddlerでバックエンドAPIへのリクエストを見ると、ロールをはじめとしてアクセストークンが表していた情報がバックエンドAPIにHTTPヘッダで渡されていることが確認できます。
（accountという、Keycloakがユーザ自身の操作用にデフォルトで定義しているクライアントの情報も混ざっています）

```http
X-Auth-Audience: gatekeeper,account
X-Auth-Email: 
X-Auth-Roles: USER,account:manage-account,account:manage-account-links,account:view-profile
X-Auth-Userid: staff
X-Auth-Username: staff
```

![kcgk_fiddler_headers.png](https://qiita-image-store.s3.amazonaws.com/0/321207/0b22b876-5a57-51c0-5ba3-1d6d66298bad.png)

- /admin → アクセス失敗『403 Forbidden』

![kcgk_app_403.png](https://qiita-image-store.s3.amazonaws.com/0/321207/4d2d0587-539e-d7c1-f58b-e428dc74ac11.png)

#### adminでログイン時の動作

最後に、USERロールに加えてADMINロールを持っているadminアカウントでログインした状態でのアクセス結果です。

- /public → アクセス成功『Message: public』
  - staffユーザでの/publicと同じ結果
- /secured → アクセス成功『Message: secured』
  - staffユーザでの/securedと同じ結果
- /admin → アクセス成功『Message: admin』

![kcgk_app_admin.png](https://qiita-image-store.s3.amazonaws.com/0/321207/4f0f3b52-8093-f1ac-a117-a8c7c2dd0fc9.png)

adminユーザで全てのAPIにアクセスが成功することが確認できました。

## おわりに

### 試してみた感想

新機能ということで設定に戸惑うこともありましたが、[Keycloak Gatekeeperのドキュメント](https://keycloak-documentation.openstandia.jp/master/ja_JP/securing_apps/index.html#_keycloak_generic_adapter)がとても頼りになりました。
ただ、[Keycloakにaudクレームの設定を追加する](#keycloak%E3%81%ABaud%E3%82%AF%E3%83%AC%E3%83%BC%E3%83%A0%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B) に記した問題を見ても分かる通り、Keycloak Gatekeeperは執筆時点ではアプリケーションとしてまだ成熟していない段階にあるように感じました。

今回はパフォーマンスの検証は行いませんでしたが、起動が非常に速く、認可リバースプロキシとして求められる機能は一通りそろっているので、手軽にAPIをアクセストークンで保護したい場合にはとても有用だと思います。
リリースではOpenShiftやKubernetesと連携して使えると説明されており、今後は様々な事例が増えてくるでしょう。

### 他の認可リバースプロキシとの比較

認証認可を含むAPI Gatewayパターンの実装は、以下のように様々なプロダクトが存在しています。

- AWS API Gateway、Azure API Management、Google Apigee Edge、Red Hat 3scale、Kong、etc.

Keycloak Gatekeeperのこれらに対する認可リバースプロキシとしての優位点は、次のようなポイントになると考えます。

- 機能が少ない分、軽量である（はず）
- 実績のあるKeycloakのシリーズであるため、標準仕様から逸脱する部分が少なく、新たな標準仕様やセキュリティ強化の対応が早い可能性が高い
- 認証認可サーバーがKeycloakである場合、設定や相性の問題が発生しにくく、技術サポートをまとめることができる

このようなポイントを重視する場合は、Keycloak Gatekeeperを検討してみる価値はありそうです。