※2018/12/21追記
Keycloakのバージョン4.8.0.Finalで、『Keycloakにaudクレームの設定を追加する』の章に記した問題がドキュメントにKnown Issuesとして追加されました。
はじめに
UL Systems Advent Calendar 2018の8日目です。
なぜこの記事を書いたか
Keycloak Gatekeeperは、Keycloakのアダプターモジュールとして、2018/11/14リリースの Keycloak 4.6.0.Final で新たに追加されました(リリースノート)。
まだWeb上に情報が少ないので、今回これを試してみることにしました。
Keycloak Gatekeeperとは
Keycloak GatekeeperはGo言語で実装されたアプリケーションで、APIの手前でOpenID Connectに対応する認可リバースプロキシとして動作します。
一般的に、API側で共通の処理を行いたいケースでは、個々のAPIでそれぞれ処理する方式よりも、APIの手前で共通処理を集約する方式が主流となっています。
この方式はマイクロサービスのデザインパターンの一つとして『API Gatewayパターン』と呼ばれ、個々のAPIが共通的に必要とする処理を手前のリバースプロキシで集約して行うことにより、APIの呼び出しや保護、管理をより容易にできるというメリットがあります。
Keycloak GatekeeperはAPI Gatewayパターンに則り、バックエンドのAPIに代わってアクセストークンの検証処理を行うことでOpenID ConnectのResource Serverの役割を提供します。
なおKeycloak Gatekeeperは、Keycloak Security Proxyという同様の役割を持つプロキシがDeprecatedとなり、後継として新たに作られたものです。
(参考:『Keycloakでリバプロ型構成を組んでみる(Security Proxy編)』)
試してみる
確かめたいこと
今回は、Keycloak Gatekeeperを使うことで、バックエンドのAPIに代わって認可制御を行い、アクセストークンが表す認可情報がバックエンドのAPIに渡されることを確かめます。
今回の構成
手軽さを重視して、WindowsのローカルPC上に全ての構成を準備することにします。
役割 | アプリケーション |
---|---|
認証認可サーバー | 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ともお友達になれるそうです。
認証クライアント
OpenID Connectの認証クライアント(Relying Party)を一から実装するのは手間がかかるので、今回はKeycloakのデモアプリケーションである Keycloak Quickstarts の中の『app-jee-html5』という認証クライアントを使うことにします。
これはKeycloakのJavaScriptアダプターであるkeycloak.jsを利用したHTML5+JavaScriptのアプリケーションで、Keycloakが同梱されているWildFlyにWARとしてデプロイすることによって動作します。
ネットワークキャプチャ
HTTPリクエストとレスポンスを可視化するネットワークキャプチャとして、Fiddler Web Debugger(以下、Fiddler)を使います。
バックエンドAPI
FiddlerのAutoResponder機能を使って、リクエストに応じた固定のレスポンスを設定することにより、疑似的にバックエンドAPIの振る舞いをさせます。
環境構築
以下の環境を前提とします。
- Java JDK 8
- Apache Maven 3.3.1以上
Keycloakのダウンロード、起動
Keycloakのダウンロードページで、ServerのZIPをダウンロード、解凍します。
解凍したフォルダ直下のbin/standalone.bat を起動します。
C:\example\keycloak-4.7.0.Final\bin>standalone.bat
起動後に http://localhost:8080/auth にアクセスし、『Administration Console』(以下、管理画面)から管理ユーザを作成してログイン後、以下の手順で日本語化しておきます。
- Realm Settings で masterレルムの『Themes』タブを開く
- 『Internationalization Enabled』をONにする。
- 『Default Locale』で『ja』を選択し、saveする。
- 右上の管理ユーザを選択し、『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で議論されていますが、執筆時点では結論はまだのようです)
したがって、この問題を回避するために、アクセストークンの"aud"クレームにKeycloak GatekeeperのクライアントIDをセットする設定をKeycloakに対して追加します。
- Keycloakの管理画面で、app-html5クライアントの『マッパー』タブで、『作成』する。
- 名前を付けて、マッパータイプで『Audience』を選択する。
- 『Included Client Audience』で『gatekeeper』を選択する。
- 『アクセストークンに追加』のみONにして、『保存』する。
設定後はこのようになります。
Keycloak Gatekeeperのダウンロード
上記のKeycloakのダウンロードページから GatekeeperのWindows版をダウンロードします。
ダウンロードしたファイル keycloak-gatekeeper-windows-amd64.tar.gz を解凍して、実行ファイルである keycloak-gatekeeper.exe を配置します。
この配置したフォルダを、Keycloak Gatekeeperの実行フォルダとします。
Keycloak Gatekeeperの設定
Keycloak Gatekeeperに、APIプロキシとして必要な設定を行います。
まず、Keycloak Gatekeeperの実行フォルダで、コマンドラインからヘルプを確認してみます。
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
を新規作成します。
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まとめ』)
Keycloak Gatekeeperの起動
設定ができたら、設定ファイルを指定して起動します。
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 のページから、GitまたはZIPファイルによってダウンロードします。
次に、app-jee-html5直下のconfigフォルダに、以下の内容でkeycloak.jsonを新規作成します。
{
"realm": "example",
"auth-server-url": "http://localhost:8080/auth",
"resource": "app-html5",
}
もう一つ、app-jee-html5\src\main\webapp\app.jsの以下の行を、Keycloak Gatekeeperを指すように修正します。
# 18行目
var serviceUrl = 'http://127.0.0.1:8080/service'
↓
var serviceUrl = 'http://localhost:3000'
最後にWildFly起動中に、app-jee-html5のフォルダでWildFlyへデプロイします。
C:\example\keycloak-quickstarts-4.7.0.Final\app-jee-html5>mvn clean wildfly:deploy
Fiddlerの起動
Fiddlerのダウンロードページで、求められる項目を入力してWindows版のインストーラー FiddlerSetup.exe をダウンロードし、インストールを行います。
(参考:『HTTP通信のキャプチャをとるツールFiddlerをWindowsにインストールする』)
インストール後、Fiddlerを起動します。
Fiddlerは初期状態では、起動直後からローカルPC上のネットワークキャプチャを自動的に開始しますが、今回の構成で行うリクエスト以外は不要なので、以下の手順でlocalhostをホストとするリクエストのみを表示する設定を行います。
- Filtersタブを表示します。
- 『Use Filters』をONにします。
- 『Hosts』の2つ目のプルダウンから『Show only the following Hosts』を選択します。
- 上記プルダウンの直後の入力項目に『localhost』を入力します。
- 最後に『Actions』→『Run Filterset now』を押して設定を有効化します。
Fiddlerの設定
Fiddlerの 『AutoResponder』タブで『Add Rule』として、以下の内容のようにpublic、secured、adminの3つのAPIの設定を行います。
-
リクエスト(下段の『Request URL Pattern』に入力する)
EXACT:http://localhost:8888/public
EXACT:http://localhost:8888/secured
EXACT:http://localhost:8888/admin
-
レスポンス(『Edit Response...』で『Raw』タブに入力する)
-
このレスポンスにも、CORSを許可するAccess-Control-Allow-Originヘッダが必要です。
-
Content-Lengthにはレスポンスボディの長さを設定します。
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-Type: application/json Content-Length: 20 {"message":"public"}
HTTP/1.1 200 OK Access-Control-Allow-Origin: * Content-Type: application/json Content-Length: 21 {"message":"secured"}
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 を操作して、動作確認を行います。
未ログイン時の動作
まず、未ログインの状態でのアクセス結果です。
- /public → アクセス成功『Message: public』
- /secured → アクセス失敗『401 Unauthorized』
- /admin → アクセス失敗『401 Unauthorized』
- /securedと同じ結果
staffでログイン時の動作
次に、USERロールを持っているstaffアカウントでログインした状態でのアクセス結果です。
LOGINボタンでKeycloakのログイン画面で、staff/staffpwdでログインしてから操作します。
- /public → アクセス成功『Message: public』
- /secured → アクセス成功『Message: secured』
ここでFiddlerでバックエンドAPIへのリクエストを見ると、ロールをはじめとしてアクセストークンが表していた情報がバックエンドAPIにHTTPヘッダで渡されていることが確認できます。
(accountという、Keycloakがユーザ自身の操作用にデフォルトで定義しているクライアントの情報も混ざっています)
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
- /admin → アクセス失敗『403 Forbidden』
adminでログイン時の動作
最後に、USERロールに加えてADMINロールを持っているadminアカウントでログインした状態でのアクセス結果です。
- /public → アクセス成功『Message: public』
- staffユーザでの/publicと同じ結果
- /secured → アクセス成功『Message: secured』
- staffユーザでの/securedと同じ結果
- /admin → アクセス成功『Message: admin』
adminユーザで全てのAPIにアクセスが成功することが確認できました。
おわりに
試してみた感想
新機能ということで設定に戸惑うこともありましたが、Keycloak Gatekeeperのドキュメントがとても頼りになりました。
ただ、Keycloakにaudクレームの設定を追加する に記した問題を見ても分かる通り、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を検討してみる価値はありそうです。