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 ファイルで宣言されたユーザーでログインできるようになります。このユーザーは、ログイン後にのみアカウントページを表示できます。