Quarkus Security with Jakarta Persistence

Quarkusには、 JDBC アイデンティティープロバイダー に似たJakarta Persistence(旧称JPA)アイデンティティープロバイダーがあり、ユーザー名とパスワードの組み合わせが必要な Basic および フォームベース のQuarkusセキュリティメカニズムで使用するのに適しています。

Jakarta Persistence IdentityProvider は、 SecurityIdentity インスタンスを作成します。このインスタンスは、ユーザー認証時に使用され、アクセス要求の検証と認可を行い、Quarkusアプリケーションを保護します。

Basic認証とJakarta Persistenceの実用的な使用例については、 Basic認証とJakarta Persistenceを使用したQuarkusアプリケーションの保護 チュートリアルを参照してください。

Jakarta Persistenceエンティティ仕様

Quarkus securityには、ユーザー名、パスワード、ロールを収集し、Jakarta Persistenceデータベースエンティティに保存するためのJakarta Persistence統合があります。

次のJakarta Persistenceエンティティの定義は、Quarkusがデータベースからこの情報を取得できるように、ユーザーの情報をJakarta Persistenceエンティティに格納し、適切にマッピングする必要があることを示すものです。

  • @UserDefinition アノテーションは、 簡略化されたHibernate ORM with Panache が使用されているかどうかに関係なく、Jakarta Persistenceエンティティに存在する必要があります。

  • @Username と @Password のフィールドの型は常に String です。

  • @Roles フィールドは、 String 、 Collection<String> 、または Collection<X> のいずれかである必要があります。 X は、 @RolesValue としてアノテーションされた単一の String フィールドを持つエンティティクラスです。

  • 各 String ロール要素タイプは、ロールのカンマ区切りリストとしてパースされます。

次の例では、 user エンティティにアノテーションを追加して、セキュリティ情報の保存をデモします:

package org.acme.security.jpa;

import jakarta.persistence.Entity;
import jakarta.persistence.Table;

import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;

@Entity
@Table(name = "test_user")
@UserDefinition (1)
public class User extends PanacheEntity {
    @Username (2)
    public String username;
    @Password (3)
    public String password;
    @Roles (4)
    public String role;

    /**
     * Adds a new user to the database
     * @param username the username
     * @param password the unencrypted password (it will be encrypted with bcrypt)
     * @param role the comma-separated roles
     */
    public static void add(String username, String password, String role) { (5)
        User user = new User();
        user.username = username;
        user.password = BcryptUtil.bcryptHash(password);
        user.role = role;
        user.persist();
    }
}

security-jpa エクステンションは、単一のエンティティが @UserDefinition でアノテーションされている場合にのみ、初期化されます。

1 @UserDefinition アノテーションは、通常の Hibernate ORM エンティティまたは Hibernate ORM with a Panache エンティティのいずれか、単一のエンティティのみに存在する必要があります。
2 ユーザー名に使用するフィールドを示します。
3 パスワードに使用するフィールドを示します。デフォルトでは、 security-jpa は bcryptでハッシュ化されたパスワードを使用します。代わりにプレーンテキストまたはカスタムパスワードを設定することもできます。
4 ここでは対象のプリンシパル表現属性に追加されたロールのコンマ区切りリストを示します。
5 このメソッドでは、パスワードを適切な bcrypt ハッシュでハッシュ化しながらユーザーを追加することができます。

ロールの保存先としてのjakarta Persistenceエンティティ

以下の例を使用して、ロールを別のJakarta Persistenceエンティティ内に格納します:

@UserDefinition
@Table(name = "test_user")
@Entity
public class User extends PanacheEntity {
    @Username
    public String name;

    @Password
    public String pass;

    @ManyToMany
    @Roles
    public List<Role> roles = new ArrayList<>();
}

@Entity
public class Role extends PanacheEntity {

    @ManyToMany(mappedBy = "roles")
    public List<ExternalRolesUserEntity> users;

    @RolesValue
    public String role;
}

パスワードの保存とハッシュ化

Quarkusでアプリケーションを開発する場合、パスワードのストレージとハッシュ化の管理方法を決めることができます。Quarkusのデフォルトのパスワードとハッシュの設定を維持するか、パスワードを手動でハッシュ化するかを選択できます。

デフォルトのオプションでは、パスワードは bcrypt でハッシュ化され、 Modular Crypt Format(MCF)で保存されます。MCFを使用している場合、ハッシュアルゴリズム、反復回数、およびソルトは、ハッシュ値の一部として保存されます。そのため、これらを保持するための専用カラムは必要ありません。

暗号技術において、ソルトとは、データ、パスワード、パスフレーズをハッシュ化する一方向性関数への追加入力として使用されるランダムデータの名称です。

異なるハッシュ・アルゴリズムを使ってハッシュ化されたデータベースに保存されたパスワードを表現するには、以下の例に示すように、 org.wildfly.security.password.PasswordProvider を実装するクラスを作成します。

次のスニペットは、SHA256ハッシュアルゴリズムでハッシュされたパスワードを表すカスタムパスワードプロバイダを設定する方法を示しています。

import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordProvider;

@UserDefinition
@Table(name = "test_user")
@Entity
public class CustomPasswordUserEntity {
    @Id
    @GeneratedValue
    public Long id;

    @Column(name = "username")
    @Username
    public String name;

    @Column(name = "password")
    @Password(value = PasswordType.CUSTOM, provider = CustomPasswordProvider.class)
    public String pass;

    @Roles
    public String role;
}

public class CustomPasswordProvider implements PasswordProvider {
    @Override
    public Password getPassword(String passwordInDatabase) {
        byte[] digest = DatatypeConverter.parseHexBinary(passwordInDatabase);

        // Let the security runtime know that this passwordInDatabase is hashed using the SHA256 hashing algorithm
        return SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256, digest);
    }
}

ハッシュ化されたパスワードを素早く作成するには、 String BcryptUtil.bcryptHash(String password) を使用します。デフォルトでは、ランダムなソルトが作成され、10回の反復でハッシュ化されます。このメソッドでは、希望する反復回数と使用するソルトを指定することも可能です。

本番環境で動作するアプリケーションでは、パスワードをプレーンテキストとして保存しないでください。

ただし、テスト環境で運用する場合は、 @Password(PasswordType.CLEAR) のアノテーションでパスワードを平文で保存することが可能です。