Quarkus Security with Jakarta Persistence

您可以配置应用程序以使用 Jakarta Persistence 存储用户的身份。 Quarkus 提供了一个 Jakarta Persistence 身份提供程序,类似于 JDBC identity provider。Jakarta Persistence 适用于使用需要用户名和密码凭据的 BasicForm-basedQuarkus 安全机制。 Jakarta Persistence `IdentityProvider`创建 `SecurityIdentity`实例。在用户身份验证期间,将使用此实例验证和授权访问请求。 有关实际示例,请参阅 Getting started with Security using Basic authentication and Jakarta Persistence教程。

Jakarta Persistence entity specification

Quarkus 安全功能提供了 Jakarta Persistence 集成,以收集用户名、密码和角色,并将它们存储到 Jakarta Persistence 数据库实体中。

以下 Jakarta Persistence 实体规范演示了如何将用户的详细信息存储在 Jakarta Persistence 实体中,并正确映射,以便 Quarkus 可以从数据库中检索该信息。

  • 无论是否使用 simplified Hibernate ORM with Panache,`@UserDefinition`注释都必须存在于 Jakarta Persistence 实体上。

  • @Username`和 `@Password`字段类型始终为 `String

  • @Roles`字段必须是 `StringCollection<String>`或 `Collection<X>,其中 X`是实体类,其中单个 `String`字段被注释为 `@RolesValue

  • 每个 `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 is 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();
    }
}

仅当单个实体使用 `@UserDefinition`进行注释时,才会初始化 `quarkus-security-jpa`扩展。

1 `@UserDefinition`注释必须存在于单个实体中,该实体可以是普通的 Hibernate ORM 实体,也可以是带有 Panache 实体的 Hibernate ORM。
2 指示用于用户名的字段。
3 指示用于密码的字段。默认情况下,`quarkus-security-jpa`使用 bcrypt 散列密码,或者你可以配置纯文本或自定义密码。
4 这表示添加到目标主体表示属性的逗号分隔角色列表。
5 此方法允许你在使用正确的 `bcrypt`散列散列密码时添加用户。

Jakarta Persistence entity as storage of roles

使用以下示例将角色存储在另一个 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<User> users;

    @RolesValue
    public String role;
}

此示例演示如何存储和访问角色。要更新现有用户或创建新用户,请使用 public List<Role> roles`配合 `@Cascade(CascadeType.ALL)`添加注释,或选择特定的 `CascadeType

Password storage and hashing

在使用 Quarkus 开发应用程序时,你可以决定如何管理密码存储和散列。你可以保留 Quarkus 的默认密码和散列设置,也可以手动散列密码。

使用默认选项时,密码会使用 bcryptModular Crypt Format (MCF) 下存储并散列。在使用 MCF 时,散列算法、迭代计数和盐会作为散列值的一部分进行存储。因此,我们不需要专门的列来保存它们。

在密码学中,盐是一个名称,表示用作单向函数(散列数据、密码或密码短语)的附加输入的随机数据。

要表示由不同算法散列的存储在数据库中的密码,请创建一个实现 `org.wildfly.security.password.PasswordProvider`的类,如以下示例所示。

以下代码段展示了如何设置一个自定义密码提供器,该提供器表示使用 SHA256 散列算法散列的密码。

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.PasswordType;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;

@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;
}
import jakarta.xml.bind.DatatypeConverter;

import org.wildfly.security.password.Password;
import org.wildfly.security.password.interfaces.SimpleDigestPassword;

import io.quarkus.security.jpa.PasswordProvider;

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 by using the SHA256 hashing algorithm
        return SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256, digest);
    }
}

要快速创建散列密码,请使用 String BcryptUtil.bcryptHash(String password),其默认操作是创建随机盐并进行十次迭代散列。此方法还允许指定迭代次数和使用的盐。

对于在生产环境中运行的应用程序,请勿将密码存储为纯文本。 但是,在测试环境中操作时,可以用 `@Password(PasswordType.CLEAR)`注释将密码存储为纯文本。

Hibernate Multitenancy受支持,你可以将用户实体存储在启用了多租户的持久性单元中。但是,如果你的 `io.quarkus.hibernate.orm.runtime.tenant.TenantResolver`必须访问 `io.vertx.ext.web.RoutingContext`以解析请求详细信息,则必须禁用主动身份验证。有关主动身份验证的详细信息,请参阅 Quarkus Proactive authentication指南。

Unresolved include directive in modules/ROOT/pages/security-jpa.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-security-jpa.adoc[]