7.8. 実装ストラテジーのインポート
ユーザーストレージプロバイダーを実装する場合は、別のストラテジーを使用できます。ユーザーフェデレーションストレージを使用する代わりに、Red Hat Single Sign-On ビルトインユーザーデータベースでユーザーをローカルで作成し、外部ストアからこのローカルコピーに属性をコピーします。この方法には多くの利点があります。
- Red Hat Single Sign-On は基本的に外部ストアの永続ユーザーキャッシュになります。ユーザーがインポートされると、外部ストアに到達できなくなるため、負荷はなくなります。
- Red Hat Single Sign-On に正式なユーザーストアとして移行し、古い外部ストアの使用を終了する場合は、Red Hat Single Sign-On を使用するようにアプリケーションを徐々に移行できます。すべてのアプリケーションが移行されたら、インポートされたユーザーのリンクを解除し、古いレガシー外部ストアを破棄します。
インポートストラテジーの使用には、いくつかの明確な欠点があります。
- ユーザーを初めて検索するには、Red Hat Single Sign-On データベースを複数回更新する必要があります。これは、負荷がある場合にパフォーマンスが大幅に低下し、Red Hat Single Sign-On データベースに大きな負担をかける可能性があります。ユーザーフェデレーションされたストレージアプローチは、必要に応じて追加のデータのみを保存し、外部ストアの機能によっては使用されない可能性があります。
- このインポート方法では、ローカルの Red Hat Single Sign-On ストレージおよび外部ストレージを同期する必要があります。User Storage SPI には同期をサポートするために実装できる機能インターフェイスがありますが、この操作はすぐに面倒で複雑になる可能性があります。
インポートストラテジーを実装するには、ユーザーがローカルにインポートされているかどうかを最初に確認します。ローカルにインポートされている場合には、ローカルユーザーを返します。インポートされていない場合には、ローカルにユーザーを作成して、外部ストアからデータをインポートします。また、ほとんどの変更が自動同期されるように、ローカルユーザーをプロキシー化することも可能です。
これは少し複雑になりますが、PropertyFileUserStorageProvider
を拡張してこのアプローチを取ることができます。最初に createAdapter()
メソッドを修正します。
PropertyFileUserStorageProvider
protected UserModel createAdapter(RealmModel realm, String username) { UserModel local = session.userLocalStorage().getUserByUsername(username, realm); if (local == null) { local = session.userLocalStorage().addUser(realm, username); local.setFederationLink(model.getId()); } return new UserModelDelegate(local) { @Override public void setUsername(String username) { String pw = (String)properties.remove(username); if (pw != null) { properties.put(username, pw); save(); } super.setUsername(username); } }; }
この方法では、KeycloakSession.userLocalStorage()
メソッドを呼び出して、ローカルの Red Hat Single Sign-On ユーザーストレージへの参照を取得します。ユーザーがローカルに保存されているかどうかを確認して、ない場合には、ローカルに追加します。ローカルユーザーの id
を設定しないでください。Red Hat Single Sign-On が id
を自動的に生成します。また、UserModel.setFederationLink()
を呼び出して、プロバイダーの ComponentModel
に ID を渡すことにも注意してください。これにより、プロバイダーとインポートされたユーザーの間にリンクが設定されます。
ユーザーストレージプロバイダーが削除されると、そのストレージプロバイダーによってインポートされたユーザーも削除されます。これは、UserModel.setFederationLink()
を呼び出す目的の 1 つです。
また、ローカルユーザーがリンクしている場合は、CredentialInputValidator
インターフェイスおよび CredentialInputUpdater
インターフェイスから実装するメソッドのために、ストレージプロバイダーが依然として委譲される点に留意してください。検証または更新から false
を返すと、Red Hat Single Sign-On はローカルストレージを使用して検証または更新できるかどうかを確認できます。
また、org.keycloak.models.utils.UserModelDelegate
クラスを使用してローカルユーザーをプロキシー処理している点に注意してください。このクラスは UserModel
の実装です。すべてのメソッドは、それがインスタンス化された UserModel
に委譲するだけです。プロパティーファイルと自動的に同期するため、この委譲クラスの setUsername()
メソッドが上書きされます。プロバイダーの場合、これを使用して、ローカル UserModel
上の他のメソッドを 傍受 して、外部ストアと同期できます。たとえば、get メソッドでは、ローカルストアが同期していることを確認することができます。set メソッドでは、外部ストアがローカルストアと同期し続けます。getId()
メソッドは、ユーザーをローカルで作成したときに自動生成された id を常に返す必要がある点に留意してください。インポート以外の他の例で示されているように、フェデレーション ID は返さないはずです。
プロバイダーが UserRegistrationProvider
インターフェイスを実装している場合、removeUser()
メソッドはローカルストレージからユーザーを削除する必要はありません。ランタイムはこの操作を自動的に実行します。また、ローカルストレージから削除される前に removeUser()
が呼び出されることに注意してください。
7.8.1. ImportedUserValidation インターフェイス
本章の最初の部分で、ユーザーへの問い合わせがどのように機能するかを説明しました。最初にローカルストレージを問い合わせし、ユーザーが見つかった場合は、クエリーが終了します。これは、上記の実装で問題になります。ローカル UserModel
をプロキシーして、ユーザー名の同期を維持します。User Storage SPI には、リンクされたローカルユーザーがローカルデータベースから読み込まれるたびに実行されるコールバックがあります。
package org.keycloak.storage.user; public interface ImportedUserValidation { /** * If this method returns null, then the user in local storage will be removed * * @param realm * @param user * @return null if user no longer valid */ UserModel validate(RealmModel realm, UserModel user); }
リンクされたローカルユーザーが読み込まれるたびに、ユーザーストレージプロバイダークラスがこのインターフェイスを実装している場合は、validate()
メソッドが呼び出されます。ここでは、ローカルユーザーをパラメーターとしてプロキシー化して返すことができます。その新しい UserModel
が使用されます。オプションで、ユーザーが外部ストアにまだ存在するかどうかを確認することもできます。validate()
が Null
を返すと、ローカルユーザーがこのデータベースから削除されます。
7.8.2. ImportSynchronization インターフェイス
インポートストラテジーにより、ローカルユーザーコピーが外部ストレージと同期できなくなっていることがわかります。おそらくユーザーは外部ストアから削除されてしまっているようです。ユーザーストレージ SPI には、これに対応するために実装できる追加のインターフェイス (org.keycloak.storage.user.ImportSynchronization
) があります。
package org.keycloak.storage.user; public interface ImportSynchronization { SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model); SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model); }
このインターフェイスはプロバイダーファクトリーによって実装されます。このインターフェイスがプロバイダーファクトリーによって実装されると、プロバイダーの管理コンソール管理ページには追加オプションが表示されます。ボタンをクリックして、手動で強制的に同期させることができます。これにより、ImportSynchronization.sync()
メソッドが呼び出されます。また、同期を自動的にスケジュールできる追加の設定オプションが表示されます。自動同期は syncSince()
メソッドを呼び出します。