7.4. パッケージおよびデプロイメント
Red Hat Single Sign-On がプロバイダーを認識できるようにするには、ファイルを JAR (META-INF/services/org.keycloak.storage.UserStorageProviderFactory
) に追加する必要があります。このファイルには、UserStorageProviderFactory
実装の完全修飾クラス名の行区切りリストが含まれている必要があります。
org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
この jar をデプロイするには、standalone/deployments/
ディレクトリーにコピーします。=== Simple read-only および lookup の例
User Storage SPI を実装する際の基本事項について、簡単な例を追って説明します。本章では、簡単なプロパティーファイルでユーザーを検索する簡単な UserStorageProvider
の実装を説明します。プロパティーファイルにはユーザー名とパスワードの定義が含まれ、クラスパス上の特定の場所にハードコーディングされます。プロバイダーは、ID およびユーザー名でユーザーを検索でき、パスワードを検証することもできます。このプロバイダーから発信されるユーザーは読み取り専用です。
7.4.1. プロバイダークラス
最初に説明するのは UserStorageProvider
クラスです。
public class PropertyFileUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator, CredentialInputUpdater { ... }
プロバイダークラス PropertyFileUserStorageProvider
は多くのインターフェイスを実装します。SPI のベース要件であるように UserStorageProvider
を実装します。このプロバイダーが保存したユーザーでログインできるため、UserLookupProvider
インターフェイスを実装します。ログイン画面で入力されたパスワードを検証する必要があるため、CredentialInputValidator
インターフェイスを実装します。プロパティーファイルは読み取り専用です。ユーザーがパスワードを更新しようとするとエラー条件を投稿するため、CredentialInputUpdater
を実装します。
protected KeycloakSession session; protected Properties properties; protected ComponentModel model; // map of loaded users in this transaction protected Map<String, UserModel> loadedUsers = new HashMap<>(); public PropertyFileUserStorageProvider(KeycloakSession session, ComponentModel model, Properties properties) { this.session = session; this.model = model; this.properties = properties; }
このプロバイダークラスのコンストラクターは KeycloakSession
、ComponentModel
、およびプロパティーファイルへの参照を保存します。後ほどこれらすべてを使用します。また、読み込んだユーザーのマップがあることにも注意してください。ユーザーを特定すると、このマップに保存し、同じトランザクションで再度ユーザーが作成されないようにします。多数のプロバイダーがこれを行う必要があるので (JPA と統合しているプロバイダー)、この慣習は適切です。また、プロバイダークラスインスタンスはトランザクションごとに 1 度作成され、トランザクションの完了後に閉じられることにも注意してください。
7.4.1.1. UserLookupProvider 実装
@Override public UserModel getUserByUsername(String username, RealmModel realm) { UserModel adapter = loadedUsers.get(username); if (adapter == null) { String password = properties.getProperty(username); if (password != null) { adapter = createAdapter(realm, username); loadedUsers.put(username, adapter); } } return adapter; } protected UserModel createAdapter(RealmModel realm, String username) { return new AbstractUserAdapter(session, realm, model) { @Override public String getUsername() { return username; } }; } @Override public UserModel getUserById(String id, RealmModel realm) { StorageId storageId = new StorageId(id); String username = storageId.getExternalId(); return getUserByUsername(username, realm); } @Override public UserModel getUserByEmail(String email, RealmModel realm) { return null; }
getUserByUsername()
メソッドは、ユーザーがログインすると Red Hat Single Sign-On ログインページにより呼び出されます。実装では最初に loadedUsers
マップを確認し、ユーザーがこのトランザクション内にすでに読み込まれているかどうかを確認します。読み込まれていない場合は、ユーザー名のプロパティーファイルを探します。UserModel
の実装を作成する場合は、将来のの参照のために loadedUsers
に保存し、このインスタンスを返します。
createAdapter()
メソッドはヘルパークラス org.keycloak.storage.adapter.AbstractUserAdapter
を使用します。これにより、UserModel
のベース実装が提供されます。外部 ID としてユーザーのユーザー名を使用して、必要なストレージ ID 形式に基づいてユーザー ID を自動的に生成します。
"f:" + component id + ":" + username
AbstractUserAdapter
のすべての get メソッドは、Null または空のコレクションを返します。ただし、ロールマッピングおよびグループマッピングを返すメソッドは、すべてのユーザーのレルムに設定されたデフォルトのロールおよびグループを返します。AbstractUserAdapter
のすべてのセットメソッドは、org.keycloak.storage.ReadOnlyException
を発生させます。そのため、管理コンソールでユーザーを変更しようとすると、エラーが発生します。
getUserById()
メソッドは、org.keycloak.storage.StorageId
ヘルパークラスを使用して id
パラメーターを解析します。StorageId.getExternalId()
メソッドが呼び出され、id
パラメーターに埋め込まれたユーザー名を取得します。その後、メソッドは getUserByUsername()
に委譲します。
メールは保存されないため、getUserByEmail()
メソッドは null を返します。
7.4.1.2. CredentialInputValidator 実装
次に、CredentialInputValidator
のメソッド実装を確認します。
@Override public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { String password = properties.getProperty(user.getUsername()); return credentialType.equals(PasswordCredentialModel.TYPE) && password != null; } @Override public boolean supportsCredentialType(String credentialType) { return credentialType.equals(PasswordCredentialModel.TYPE); } @Override public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { if (!supportsCredentialType(input.getType())) return false; String password = properties.getProperty(user.getUsername()); if (password == null) return false; return password.equals(input.getChallengeResponse()); }
isConfiguredFor()
メソッドはランタイムによって呼び出され、特定の認証情報タイプがユーザーに設定されているかどうかを判断します。この方法は、パスワードがユーザーに設定されていることを確認します。
supportsCredentialType()
メソッドは、特定の認証情報タイプに対して検証がサポートされるかどうかを返します。認証情報のタイプが password
であるかを確認します。
isValid()
メソッドはパスワードを検証します。CredentialInput
パラメーターは、すべての認証情報タイプの抽象インターフェイスにすぎません。認証情報タイプもサポートし、UserCredentialModel
のインスタンスであることを確認します。ユーザーがログインページでログインすると、UserCredentialModel
のインスタンスに入力されたパスワード入力のプレーンテキストが追加されます。isValid()
メソッドは、この値をプロパティーファイルに保存されているプレーンテキストのパスワードと照合します。戻り値 true
はパスワードが有効であることを意味します。
7.4.1.3. CredentialInputUpdater 実装
前述したように、CredentialInputUpdater
インターフェイスを実装する唯一の理由は、ユーザーパスワードの変更を禁止することです。このようにする理由として、Red Hat Single Sign-On のローカルストレージでランタイムによりパスワードを上書きできるためです。本章の後半で詳しく説明します。
@Override public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { if (input.getType().equals(PasswordCredentialModel.TYPE)) throw new ReadOnlyException("user is read only for this update"); return false; } @Override public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { } @Override public Set<String> getDisableableCredentialTypes(RealmModel realm, UserModel user) { return Collections.EMPTY_SET; }
updateCredential()
メソッドは、認証情報タイプがパスワードであるかどうかを確認します。存在する場合は、ReadOnlyException
が発生します。
7.4.2. プロバイダーファクトリーの実装
プロバイダークラスが完成したので、プロバイダーファクトリークラスを見ていきます。
public class PropertyFileUserStorageProviderFactory implements UserStorageProviderFactory<PropertyFileUserStorageProvider> { public static final String PROVIDER_NAME = "readonly-property-file"; @Override public String getId() { return PROVIDER_NAME; }
最初に UserStorageProviderFactory
クラスを実装する場合は、具体的なプロバイダークラス実装をテンプレートパラメーターとして渡す必要があります。ここで、以前に定義したプロバイダークラス (PropertyFileUserStorageProvider
) を指定します。
template パラメーターを指定しない場合には、プロバイダーは機能しません。ランタイムはクラスのイントロスペクションを実行し、プロバイダーが実装する 機能インターフェイス を判別します。
getId()
メソッドはランタイムのファクトリーを識別し、レルムのユーザーストレージプロバイダーを有効にする場合も管理コンソールに表示される文字列になります。
7.4.2.1. 初期化
private static final Logger logger = Logger.getLogger(PropertyFileUserStorageProviderFactory.class); protected Properties properties = new Properties(); @Override public void init(Config.Scope config) { InputStream is = getClass().getClassLoader().getResourceAsStream("/users.properties"); if (is == null) { logger.warn("Could not find users.properties in classpath"); } else { try { properties.load(is); } catch (IOException ex) { logger.error("Failed to load users.properties file", ex); } } } @Override public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) { return new PropertyFileUserStorageProvider(session, model, properties); }
UserStorageProviderFactory
インターフェイスには、実装できる任意の init()
メソッドがあります。Red Hat Single Sign-On の起動時に、各プロバイダーファクトリーのインスタンスが 1 つだけ作成されます。また、システムの起動時に、これらの各ファクトリーのインスタンスでも init()
メソッドが呼び出されます。同様に実装できる postInit()
メソッドも実装できます。各ファクトリーの init()
メソッドが呼び出されると、postInit()
メソッドが呼び出されます。
init()
メソッド実装では、クラスパスからユーザー宣言が含まれるプロパティーファイルを見つけます。次に properties
フィールドを、そこに保存されたユーザー名とパスワードの組み合わせでロードします。
Config.Scope
パラメーターは、サーバー設定を介して設定されるファクトリー設定です。
たとえば、以下を standalone.xml
に追加した場合は以下のようになります。
<spi name="storage"> <provider name="readonly-property-file" enabled="true"> <properties> <property name="path" value="/other-users.properties"/> </properties> </provider> </spi>
ハードコーディングするのではなく、ユーザープロパティーファイルのクラスパスを指定できます。次に、PropertyFileUserStorageProviderFactory.init()
で設定を取得できます。
public void init(Config.Scope config) { String path = config.get("path"); InputStream is = getClass().getClassLoader().getResourceAsStream(path); ... }
7.4.2.2. 作成方法
プロバイダーファクトリー作成における最後の手順は create()
メソッドです。
@Override public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) { return new PropertyFileUserStorageProvider(session, model, properties); }
PropertyFileUserStorageProvider
クラスを割り当てるだけです。この create メソッドはトランザクションごとに 1 回呼び出されます。
7.4.3. パッケージおよびデプロイメント
プロバイダー実装のクラスファイルは jar に配置する必要があります。また、META-INF/services/org.keycloak.storage.UserStorageProviderFactory
ファイルでプロバイダーファクトリークラスを宣言する必要があります。
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
この jar をデプロイするには、standalone/deployments/
ディレクトリーにコピーします。
7.4.4. 管理コンソールでのプロバイダーの有効化
管理コンソールの User Federation ページで、レルムごとにユーザーストレージプロバイダーを有効にします。
手順
readonly-property-file
リストから作成したプロバイダーを選択します。プロバイダーの設定ページが表示されます。
- 設定するものがないため、Save をクリックします。
User Federation のメインページに戻ります。
これで、プロバイダーが一覧表示されます。
これで、users.properties
ファイルで宣言されたユーザーでログインできるようになります。このユーザーは、ログイン後にのみアカウントページを表示できます。