Java
development
SSO
Keycloak

Keycloak 開発入門

初版: 2018/8/1

著者: 茂木 昂士, 株式会社日立製作所


はじめに

Keycloak(https://www.keycloak.org/)はオープンソースのアイデンティティ・アクセス管理ソフトウェアです。コミュニティベースで開発が行われているため誰でもKeycloakの開発に貢献することができますが、開発方法やコミュニティのルールが分からないとなかなか手を出せないと思います。そこで今回は、私が行ったパッチ開発(#5163)の内容を通して、得られた知見などを共有していきたいと思います。

なおこれらの情報は、KeycloakのリポジトリにあるREADME.mdHackingOnKeycloak.md, HOW-TO-RUN.mdといった内容をもとにしています。

この記事はOSSセキュリティ技術の会 第三回勉強会 で発表した内容に加筆したものです。


目次


開発した内容

KeycloakにはIdentity Brokeringという機能があり、外部のOpenID Connnect Providerで認証した結果をKeycloakで利用することができます。KeycloakにはGoogleやFacebookといった一般的なプロバイダーに接続するための設定も準備されています。

brokering.png

ユーザの認証処理は画面も含めて自前で行いたいという要件があったため、Identity Brokeringを利用して独自の認証アプリケーションにリダイレクトする設定にしました。しかしこの認証アプリケーションでは、クライアントからOpenID Connectの仕様外のパラメータを送信してもらう必要があったのですが、KeycloakのIdentity Brokeringでは独自に指定したいQueryParameterが外部Identity Provider(=認証画面)に転送されませんでした。

これを解決するため、Identity Provider SPI(Identity Brokeringを拡張するためのインターフェース)を使って、パラメータを転送するIdentity Providerを独自に実装しました。 しかし、OpenID Connect仕様外のパラメータを利用するユースケースはほかにも考えられると感じ、一般的な機能としてコミュニティにコントリビューションすることにしました。


機能の提案、バグの報告

Keycloakでは新機能の提案やバグの報告に下記のツールを利用しています。

バグなどを見つけた場合、まずはのJIRAに詳細を記載したチケットを作成し、そのチケットのIDをタイトルに入れたPull Requestを作成します。

ここからは、実際にパッチ開発をした知見をもとにして、パッチ開発環境の準備や、コード変更の方法、テスト実行の方法などを紹介していきます。


実装のための準備

まずはKeycloakのRepositoryをForkし、そのRepositoryをクローンしてきます。

$ git clone https://github.com/<username>/keycloak

このクローンしてきたディレクトリで準備をしていきます。


ビルド方法

ソースコードを持ってきたらまずはビルドを行います。ビルドのためにはJDKやMavenが必要になるので、それぞれセットアップを行ってください。

ソースコードのビルドは下記のコマンドで行います。

$ mvn install -DskipTests=true

$ cd distribution
$ mvn install

テストは環境によって失敗することがあるので、ビルド時はテストを飛ばし修正した後に関連するテストだけを走らせるのが良いと思います。

上記コマンドを実行すると、distribution/server-dist/target以下にビルド物(.tar.gz, .zip)が出力されます。これらを展開し、bin/standalone.shを実行することでKeycloakを実行することもできます。


IDEへのインポート

次にIDEで開発を行うための準備を行っていきます。今回はIDEとしてEclipse (Oxygen) を利用しました。Keycloakリポジトリの直下にあるpom.xmlファイル(keycloak-parentというプロジェクトになる)を指定してMavenプロジェクトをEclipseにインポートします。

プロジェクトが大量に(300以上)あるため、インポートには多少の時間がかかります。


IDE(Eclipse) でのデバッグ方法

開発中のKeycloakを起動するためには、README.mdに記載してある通りmvn -f testsuite/utils/pom.xml exec:java -Pkeycloak-serverを実行します。Keycloakサーバ起動後はlocalhost:8081/auth/からKeycloakのGUIにアクセスできます。

上記の通りコマンドラインからMavenで実行することは可能ですが、せっかくEclipseを利用しているのでそちらでデバッグを行えるようにします。上記コマンドで指定しているpom.xmlを見ると、keycloak-testsuite-utilsプロジェクトのorg.keycloak.testsuite.KeycloakServerというクラスを実行しているので、このクラスを指定してデバッグを実行します。設定は下図のようになります。

debug_config.png

これでEclipseを使ってKeycloakのデバッグができるようになります。

さらにデータベースの設定を行います。デフォルトの設定ではH2DBのInMemoryDBが利用されますが、このままでは起動しなおすたびに設定が消えてしまいます。何回も設定しなおすのは面倒なのでファイルに保存する設定にします。起動時の引数として-Dkeycloak.connectionsJpa.url=jdbc:h2:./keycloak;AUTO_SERVER=TRUEを指定します。Eclipseでの設定方法は下図の通りです。

persist_settings.png

ここまででデバッグも含めた開発の準備ができたので、コードの修正を行っていきます。


コードの修正

ここからは、「Identity Brokering機能でカスタムパラメータを外部のIdenity Providerに転送する」という機能を実装した内容をもとに、Keycloakの簡単なアーキテクチャ説明を行っていきます。


修正の内容

KeycloakのAuthorization Endpointにアクセスがあった際のパラメータ値は、仕様外のパラメータも含めてAuthorizationSessionというモデルに保存されていました。なので、外部Identity ProviderへのRedirectURLを作成する部分に、AuthorizationSessionから仕様外のパラメータを取得してURLに追加する修正を行いました。

また、すべてのパラメータを転送するのではなく、Identity Brokeringの設定で指定されたパラメータのみを転送するようにしました。


Identity Provider関連ファイルの場所

認証、認可といった基本的な機能や、Identity Brokeringの機能は keycloak-servicesというプロジェクトに実装されています。このプロジェクトにはkeycloak-spi, keycloak-spi-privateで定義されているSPIを実装しています。SPIを利用してプラグインを開発する場合と同様に、Provider, ProviderFactoryの二つが対になって実装されています。

OpenID Connect Identity Providerはorg.keycloak.broker.oidc.OIDCIdentityProviderというクラスに実装されています。外部Identity ProviderへのRedirect URLを作成しているのは、このクラスが継承しているorg.keycloak.broker.oidc.AbstractOauth2IdentityProvidercreateAuthorizationUrlというメソッドだったので、このメソッドに修正を行いました。


GUIの変更

外部の特定のパラメータだけを転送するようにするため、Identity Providerの設定項目に転送するパラメータを設定する項目を追加しました。

label.png

KeycloakのGUIは AngularJS(1.6)で作られています。 keycloak-themesというプロジェクトの下にtemplateやJavascriptのコードが入っています。Idenity Providerの設定項目は、Formの内容をすべてAPIに送信する作りになっていたため、realm-identity-provider-oidc.htmlというテンプレートにFormとTooltipを追加するだけで実装できました。

2018/07 現在、keycloak-themes以下にkeycloak-previewというフォルダが作られていました。KeycloakのAdmin ConsoleのGUIを新しく作っているらしく、軽く眺めてみたところAngular 5で作っている模様でした。レスポンシブ対応などのPull Requestを目にしたこともあるので期待して待ちたいと思います。


DBスキーマの修正

Identity Providerの設定項目を増やしたので、DBスキーマの変更が必要だと思っていました。しかし、設定項目はIDENTITY_PROVIDER_CONFIGというテーブルにKey-Valueの形で登録されていたので、スキーマの変更は必要ありませんでした。コード上でもMap<String, String>で管理されています。設定項目は変更が多いことを考えると、スキーマ変更の必要なく対応できるこの作りは合理的だと思いました。

今回はスキーマの変更が必要ありませんでしたが、もしDB関連の変更が必要な場合はkeycloak-model-jpa プロジェクトにモデルがまとまっているので、それらを変更します。


Testの実装

KeycloakではArquiilianというテストフレームワークを利用してインテグレーションテストを行っています。Identity Brokeringのテストは、integration-arquiilian-tests-base(testsuite\integration-arquillian\tests\base)というプロジェクトに含まれています。

外部Identity Providerのテストをどのようにやるかというと、図のようにconsumerproviderという二つのRealmを利用することで対応していました。

provider-consumer.png

既存のテストを見てみると、クエリパラメータをチェックしているテストがあったので、それを流用してテストを記載しました。

実装したテストを実行するためには、下記のコマンドを実行します。

$ mvn -f testsuite/integration-arquillian/tests/base/pom.xml test

これで基本機能のテストが実行されますが、一つ一つのテストに時間がかかり、テスト数もあるのですべてのテストが終了するまでにかなりの時間がかかります。特定のテストを実行するためには、-Dtestでクラス名を指定します。

$ mvn -f testsuite/integration-arquillian/tests/base/pom.xml test -Dtest=org.keycloak.testsuite.broker.KcOidcBrokerParameterForwardTest

また、-Dtestでは正規表現を使ったテストの指定も可能です。例えば下記コマンドでは、Identity Broker関連のテストのみを実行することができます。

$ mvn -f testsuite/integration-arquillian/tests/base/pom.xml test -Dtest=org.keycloak.testsuite.broker.Kc*

今回のBaseテストは基本的な機能のテストのみでしたが、アダプターのテストやUIテスト等様々なテストが実装されています。詳細については、keycloak-testsuiteプロジェクトにあるHOW-TO-RUN.mdを参照してください。

ここまでで必要なコードの修正が終わりました。いよいよ修正をGitHubに投稿します。


GitHubへの投稿

修正したコードをGitHubにPushしてPull Requestを出すと、TravisCIでインテグレーションテストが実行されます。すべてのテストが完了するまで20分程度かかります。全Testがパスすればレビューの対象になります。

コードレビューはしっかりと見てもらえました(assertTrue/assertFalseよりassertThatのほうが見やすい、など)。最初はユニットテストも書いたのですが、インテグレーションテストですでに機能がカバーできていたため、「インテグレーションテストとユニットテストで内容が重複してるとメンテナンスコストが上がる」とコメントをもらい、不要との判断になりました。

インテグレーションテストがしっかりしているので、カバーできているならユニットテストは必要ないというポリシーのようです。

変更は最終的には1コミットにまとめてと言われたので、git rebase -iでコミットをまとめて、git push -fでPushしました。

マージされるまでに時間がかかりましたが、無事マージされました。https://github.com/keycloak/keycloak/pull/5163


まとめ

Keycloakはコミュニティの活動が活発で、新しい機能が次々と開発されていて、コミュニティ外部からの改善も積極的に取り入れています。

日本からもどんどんパッチを出して、よいOSSにしていきましょう。