Getting started with Security by using Basic authentication and Jakarta Persistence

通过内置 Quarkus Basic authentication 和 Jakarta Persistence 标识提供程序来保护 Quarkus 应用程序端点,从而开始使用 Quarkus 安全功能并启用基于角色的访问控制。 Jakarta Persistence IdentityProvider 验证和转换 Basic authentication 用户名和密码对来生成 SecurityIdentity 实例,该实例用于授权访问请求,从而确保 Quarkus 应用程序安全。 有关 Jakarta Persistence 的详细信息,请参阅 Quarkus Security with Jakarta Persistence 指南。 本教程指导您在 Quarkus 中实现更高级别的安全机制,例如如何使用 OpenID Connect (OIDC) 身份验证机制。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Building your application

本教程提供了详细的步骤来创建具有端点的应用程序,这些端点演示了各种授权策略:

Endpoint Description

/api/public

无需身份验证即可访问,该端点允许匿名访问。

/api/admin

通过基于角色的访问控制 (RBAC) 保护,只有具有 admin 角色的用户才能访问该端点。访问是通过使用 @RolesAllowed 注释以声明方式控制的。

/api/users/me

同样受 RBAC 保护,只有具有 user 角色的用户才能访问该端点。它将调用者的用户名作为字符串返回。

要检查完成的示例,请下载 $${quickstarts-base-url}/archive/main.zip[存档] 或克隆 Git 存储库:

git clone {quickstarts-clone-url}

您可以在 security-jpa-quickstart directory 中找到解决方案。

Create and verify the Maven project

为了使 Quarkus Security 能够将安全来源映射到 Jakarta Persistence 实体,确保本教程中的 Maven 项目包含 `quarkus-security-jpa`或 `quarkus-security-jpa-reactive`扩展。

Hibernate ORM with Panache用于存储您的用户身份,但您还可以使用 `quarkus-security-jpa`扩展与 Hibernate ORM一起使用。 Hibernate ReactiveHibernate Reactive with Panache都可以与 `quarkus-security-jpa-reactive`扩展一起使用。 您还必须添加您倾向的数据库连接器库。本示例教程中的说明为身份存储使用 PostgreSQL 数据库。

Create the Maven project

您可以使用安全 Jakarta Persistence 扩展创建新的 Maven 项目,也可以将扩展添加至现有的 Maven 项目。您可以使用 Hibernate ORM 或 Hibernate Reactive。

  • 要使用 Jakarta Persistence 扩展创建新的 Maven 项目,请完成以下步骤之一:

    • 要使用 Hibernate ORM 创建 Maven 项目,请使用以下命令:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

    • 要将 Jakarta Persistence 扩展添加到现有的 Maven 项目,请完成以下步骤之一:

      • 要向使用 Hibernate ORM 的现有 Maven 项目添加安全 Jakarta Persistence 扩展,请从您的项目基本目录运行以下命令:====== :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/devtools/extension-add.adoc :keywords: Quarkus, 中文文档, 编程技术

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'
  • 要向使用 Hibernate Reactive 的现有 Maven 项目添加安全 Jakarta Persistence 扩展,请从您的项目基本目录运行以下命令:====== :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/devtools/extension-add.adoc :keywords: Quarkus, 中文文档, 编程技术

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

Verify the quarkus-security-jpa dependency

运行上述任一命令以创建 Maven 项目后,验证是否已将 `quarkus-security-jpa`依存关系添加到您的项目构建 XML 文件中。

  • 要验证 `quarkus-security-jpa`扩展,请检查以下配置:======

    .pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security-jpa</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-security-jpa")
  • 要验证 `quarkus-security-jpa-reactive`扩展,请检查以下配置:======

    .pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security-jpa-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-security-jpa-reactive")

Write the application

  • 保护 API 端点以使用以下方法之一确定谁可以访问应用程序:

    • 实施 `/api/public`端点以允许所有用户访问应用程序。向您的 Java 源代码中添加常规 Jakarta REST 资源,如下图所示:======

package org.acme.security.jpa;

import jakarta.annotation.security.PermitAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/api/public")
public class PublicResource {

    @GET
    @PermitAll
    @Produces(MediaType.TEXT_PLAIN)
    public String publicResource() {
        return "public";
   }
}
  • 实施一个只有具有管理员角色的用户才能访问的 /api/admin 端点。`/api/admin`端点的源代码相似,但相反,您使用一个 `@RolesAllowed`注释来确保只有已授予 `admin`角色的用户才能访问该端点。使用以下 `@RolesAllowed`注释添加 Jakarta REST 资源:======

package org.acme.security.jpa;

import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/api/admin")
public class AdminResource {

    @GET
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String adminResource() {
         return "admin";
    }
}
  • 实施一个只有拥有 `user`角色的用户才能访问的 `/api/users/me`端点。使用 `SecurityContext`来访问当前认证的 `Principal`用户并返回他们的用户名,所有这些信息都是从数据库中检索的。======

package org.acme.security.jpa;

import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;

@Path("/api/users")
public class UserResource {

    @GET
    @RolesAllowed("user")
    @Path("/me")
    public String me(@Context SecurityContext securityContext) {
        return securityContext.getUserPrincipal().getName();
    }
}

== Define the user entity

  • 您现在可以通过向 `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();
    }
}

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

1 `@UserDefinition`注释必须存在于单个实体中,该实体可以是普通的 Hibernate ORM 实体,也可以是带有 Panache 实体的 Hibernate ORM。
2 指示用于用户名的字段。
3 指示用于密码的域。默认情况下,它使用 bcrypt 散列的密码。您可以对其进行配置以使用明文或自定义密码。
4 指示添加到目标主体表示属性的逗号分隔的角色列表。
5 允许我们在使用适当的 bcrypt 散列对密码进行散列时添加用户。

不要忘记设置 Panache 和 PostgreSQL JDBC 驱动程序,请参阅 Setting up and configuring Hibernate ORM with Panache 了解更多信息。

Hibernate Reactive Panache 使用 io.quarkus.hibernate.reactive.panache.PanacheEntity 而不是 io.quarkus.hibernate.orm.panache.PanacheEntity。有关详细信息,请参阅 User file

== Configure the application

  1. 通过将 quarkus.http.auth.basic 属性设置为 true 来启用内置 Quarkus Basic authentication 机制:quarkus.http.auth.basic=true

当需要安全访问并且未启用其他身份验证机制时,Quarkus 的内置 Basic authentication 是备用身份验证机制。因此,在本教程中,无需将属性 quarkus.http.auth.basic 设置为 true

  1. application.properties 文件中至少配置一个数据源,以便 quarkus-security-jpa 扩展程序访问您的数据库。例如:======

quarkus.http.auth.basic=true

quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=quarkus
quarkus.datasource.password=quarkus
quarkus.datasource.jdbc.url=jdbc:postgresql:security_jpa

quarkus.hibernate-orm.database.generation=drop-and-create
  1. 要使用用户和角色初始化数据库,请实现 Startup 类,如下面的代码片段所示:

  • quarkus-security-jpa-reactive 扩展程序使用的 Reactive 数据源的 URL 使用 quarkus.datasource.reactive.url 配置属性设置,而不是 JDBC 数据源通常使用的 quarkus.datasource.jdbc.url 配置属性。[source, properties]

%prod.quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost:5431/security_jpa
  • 在本教程中,使用 PostgreSQL 数据库作为身份存储。 Hibernate ORM 在启动时自动创建数据库架构。这种方法适用于开发,但不建议用于生产。因此,在生产环境中需要进行调整。

package org.acme.security.jpa;

import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;

import io.quarkus.runtime.StartupEvent;


@Singleton
public class Startup {
    @Transactional
    public void loadUsers(@Observes StartupEvent evt) {
        // reset and load all test users
        User.deleteAll();
        User.add("admin", "admin", "admin");
        User.add("user", "user", "user");
    }
}

前面的示例展示了如何保护应用程序以及由指定数据库提供的身份。

在生产环境中,不要存储纯文本密码。因此,quarkus-security-jpa 默认使用 bcrypt 哈希密码。

Test your application by using Dev Services for PostgreSQL

在生产模式下运行应用程序之前,使用 Dev Services for PostgreSQL 在 JVM 和原生模式下完成应用程序的集成测试。

首先将以下依赖项添加到测试项目:

pom.xml
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.rest-assured:rest-assured")
  • 要在开发模式下运行应用程序:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev
  • 以下属性配置演示如何启用 PostgreSQL 测试仅在生产 (prod) 模式下运行。在此场景中,Dev Services for PostgreSQL 会启动并配置一个 PostgreSQL 测试容器。

%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus
%prod.quarkus.datasource.password=quarkus
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql:elytron_security_jpa

quarkus.hibernate-orm.database.generation=drop-and-create
  • 如果您添加 %prod. 配置文件前缀,数据源属性将对 Dev Services for PostgreSQL 不可见,并且仅对在生产模式下运行的应用程序可见。

  • 要编写集成测试,请使用以下代码示例:

package org.acme.security.jpa;

import static io.restassured.RestAssured.get;
import static io.restassured.RestAssured.given;
import static org.hamcrest.core.Is.is;

import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class JpaSecurityRealmTest {

    @Test
    void shouldAccessPublicWhenAnonymous() {
        get("/api/public")
                .then()
                .statusCode(HttpStatus.SC_OK);

    }

    @Test
    void shouldNotAccessAdminWhenAnonymous() {
        get("/api/admin")
                .then()
                .statusCode(HttpStatus.SC_UNAUTHORIZED);

    }

    @Test
    void shouldAccessAdminWhenAdminAuthenticated() {
        given()
                .auth().preemptive().basic("admin", "admin")
                .when()
                .get("/api/admin")
                .then()
                .statusCode(HttpStatus.SC_OK);

    }

    @Test
    void shouldNotAccessUserWhenAdminAuthenticated() {
        given()
                .auth().preemptive().basic("admin", "admin")
                .when()
                .get("/api/users/me")
                .then()
                .statusCode(HttpStatus.SC_FORBIDDEN);
    }

    @Test
    void shouldAccessUserAndGetIdentityWhenUserAuthenticated() {
        given()
                .auth().preemptive().basic("user", "user")
                .when()
                .get("/api/users/me")
                .then()
                .statusCode(HttpStatus.SC_OK)
                .body(is("user"));
    }
}

如您在此代码示例中所见,您无需从测试代码启动测试容器。

当您在开发模式下启动应用程序时,PostgreSQL 的开发服务会启动一个 PostgreSQL 开发模式容器,以便您可以开始开发应用程序。在开发应用程序时,您可以使用 Continuous Testing 功能单独添加并运行测试。PostgreSQL 的开发服务通过提供一个单独的 PostgreSQL 测试容器(该容器与开发模式容器不冲突)来支持您在开发期间进行测试。

Use curl or a browser to test your application

  • 使用以下示例启动 PostgreSQL 服务器:

docker run --rm=true --name security-getting-started -e POSTGRES_USER=quarkus \
           -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=elytron_security_jpa \
           -p 5432:5432 postgres:14.1

Compile and run the application

  • 使用以下方法之一编译并运行您的 Quarkus 应用程序:

    • JVM mode[style="arabic"]

      1. Compile the application:====== :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/devtools/build.adoc :keywords: Quarkus, 中文文档, 编程技术

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build
  1. Run the application:======

java -jar target/quarkus-app/quarkus-run.jar
  • Native mode[style="arabic"]

    1. Compile the application:====== :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/devtools/build-native.adoc :keywords: Quarkus, 中文文档, 编程技术

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true
  1. Run the application:======

./target/security-jpa-quickstart-1.0.0-SNAPSHOT-runner

Access and test the application security

当您的应用程序正在运行时,您可以使用以下 Curl 命令之一访问其端点。

  • 以匿名方式连接到受保护端点:======

$ curl -i -X GET http://localhost:8080/api/public

HTTP/1.1 200 OK
Content-Length: 6
Content-Type: text/plain;charset=UTF-8

public
  • 以匿名方式连接到受保护端点:======

$ curl -i -X GET http://localhost:8080/api/admin

HTTP/1.1 401 Unauthorized
Content-Length: 14
Content-Type: text/html;charset=UTF-8
WWW-Authenticate: Basic

Not authorized
  • 以授权用户的身份连接到受保护端点:======

$ curl -i -X GET -u admin:admin http://localhost:8080/api/admin

HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8

admin

您还可以使用浏览器访问相同的端点 URL。

如果您使用浏览器以匿名方式连接到受保护资源,将显示一个基本身份验证表单,提示您输入凭据。

=== Results

当您提供授权用户的凭据(例如 admin:admin)时,Jakarta Persistence 安全扩展将对用户进行身份验证并加载用户的角色。admin 用户被授权访问受保护资源。

如果资源受 @RolesAllowed("user") 保护,则用户 admin 无权访问该资源,因为它未分配给“用户”角色,如下例所示:

$ curl -i -X GET -u admin:admin http://localhost:8080/api/users/me

HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8

Forbidden

最后,名为 user 的用户被授权,并且安全上下文包含主体详细信息,例如用户名。

$ curl -i -X GET -u user:user http://localhost:8080/api/users/me

HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8

user

== What’s next

您已成功学习如何创建和测试安全的 Quarkus 应用程序。这是通过将 Quarkus 中内置的 Basic authentication 与 Jakarta Persistence 标识提供程序集成而实现的。

在完成本教程后,您可以在 Quarkus 中探索更高级的安全机制。以下信息显示了如何使用 OpenID Connect 通过安全单点登录访问 Quarkus 端点:

== References