Getting started with Security by using Basic authentication and Jakarta Persistence

通过内置 Quarkus Basic authentication 和 Jakarta Persistence 标识提供程序来保护 Quarkus 应用程序端点,从而开始使用 Quarkus 安全功能并启用基于角色的访问控制。

Get started with Quarkus Security by securing your Quarkus application endpoints with the built-in Quarkus Basic authentication and the Jakarta Persistence identity provider, enabling role-based access control.

Jakarta Persistence IdentityProvider 验证和转换 Basic authentication 用户名和密码对来生成 SecurityIdentity 实例,该实例用于授权访问请求,从而确保 Quarkus 应用程序安全。

The Jakarta Persistence IdentityProvider verifies and converts a Basic authentication user name and password pair to a SecurityIdentity instance, which is used to authorize access requests, making your Quarkus application secure.

有关 Jakarta Persistence 的详细信息,请参阅 Quarkus Security with Jakarta Persistence 指南。

For more information about Jakarta Persistence, see the Quarkus Security with Jakarta Persistence guide.

本教程指导您在 Quarkus 中实现更高级别的安全机制,例如如何使用 OpenID Connect (OIDC) 身份验证机制。

This tutorial prepares you to implement more advanced security mechanisms in Quarkus, for example, how to use the OpenID Connect (OIDC) authentication mechanism.

Prerequisites

Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/prerequisites.adoc[]

Building your application

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

This tutorial gives detailed steps for creating an application with endpoints that illustrate various authorization policies:

Endpoint Description

/api/public

Accessible without authentication, this endpoint allows anonymous access.

/api/admin

Secured with role-based access control (RBAC), this endpoint is accessible only to users with the admin role. Access is controlled declaratively by using the @RolesAllowed annotation.

/api/users/me

Also secured by RBAC, this endpoint is accessible only to users with the user role. It returns the caller’s username as a string.

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

To examine the completed example, download the {quickstarts-archive-url}[archive] or clone the Git repository:

git clone {quickstarts-clone-url}

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

You can find the solution in the security-jpa-quickstart directory.

Create and verify the Maven project

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

For Quarkus Security to be able to map your security source to Jakarta Persistence entities, ensure that the Maven project in this tutorial includes the quarkus-security-jpa or quarkus-security-jpa-reactive extension.

Hibernate ORM with Panache用于存储您的用户身份,但您还可以使用 `quarkus-security-jpa`扩展与 Hibernate ORM一起使用。

Hibernate ORM with Panache is used to store your user identities, but you can also use Hibernate ORM with the quarkus-security-jpa extension.

Hibernate ReactiveHibernate Reactive with Panache都可以与 `quarkus-security-jpa-reactive`扩展一起使用。

Both Hibernate Reactive and Hibernate Reactive with Panache can be used with the quarkus-security-jpa-reactive extension.

您还必须添加您倾向的数据库连接器库。本示例教程中的说明为身份存储使用 PostgreSQL 数据库。

You must also add your preferred database connector library. The instructions in this example tutorial use a PostgreSQL database for the identity store.

Create the Maven project

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

You can create a new Maven project with the Security Jakarta Persistence extension or add the extension to an existing Maven project. You can use either Hibernate ORM or Hibernate Reactive.

  • To create a new Maven project with the Jakarta Persistence extension, complete one of the following steps:

    • To create the Maven project with Hibernate ORM, use the following command:

Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/create-app.adoc[]

  • To add the Jakarta Persistence extension to an existing Maven project, complete one of the following steps:

    • To add the Security Jakarta Persistence extension to an existing Maven project with Hibernate ORM, run the following command from your project base directory:====== Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/extension-add.adoc[]

  • To add the Security Jakarta Persistence extension to an existing Maven project with Hibernate Reactive, run the following command from your project base directory:====== Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/extension-add.adoc[]

Verify the quarkus-security-jpa dependency

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

After you have run either of the preceding commands to create the Maven project, verify that the quarkus-security-jpa dependency was added to your project build XML file.

  • To verify the quarkus-security-jpa extension, check for the following configuration:======

    .pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security-jpa</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-security-jpa")
  • To verify the quarkus-security-jpa-reactive extension, check for the following configuration:======

    .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

  • Secure the API endpoint to determine who can access the application by using one of the following approaches:

    • Implement the /api/public endpoint to allow all users access to the application. Add a regular Jakarta REST resource to your Java source code, as shown in the following code snippet:======

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";
   }
}
  • Implement an /api/admin endpoint that can only be accessed by users who have the admin role. The source code for the /api/admin endpoint is similar, but instead, you use a @RolesAllowed annotation to ensure that only users granted the admin role can access the endpoint. Add a Jakarta REST resource with the following @RolesAllowed annotation:======

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";
    }
}
  • Implement an /api/users/me endpoint that can only be accessed by users who have the user role. Use SecurityContext to get access to the currently authenticated Principal user and to return their username, all of which is retrieved from the database.======

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

  • You can now describe how you want security information to be stored in the model by adding annotations to the user entity, as outlined in the following code snippet:

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 进行注释时才初始化。

The quarkus-security-jpa extension only initializes if a single entity is annotated with @UserDefinition.

1 The @UserDefinition annotation must be present on a single entity, either a regular Hibernate ORM entity or a Hibernate ORM with Panache entity.
2 Indicates the field used for the username.
3 Indicates the field used for the password. By default, it uses bcrypt-hashed passwords. You can configure it to use plain text or custom passwords.
4 Indicates the comma-separated list of roles added to the target principal representation attributes.
5 Allows us to add users while hashing passwords with the proper bcrypt hash.

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

Don’t forget to set up the Panache and PostgreSQL JDBC driver, please see Setting up and configuring Hibernate ORM with Panache for more information.

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

Hibernate Reactive Panache uses io.quarkus.hibernate.reactive.panache.PanacheEntity instead of io.quarkus.hibernate.orm.panache.PanacheEntity. For more information, see User file.

== Configure the application

  1. Enable the built-in Quarkus Basic authentication mechanism by setting the quarkus.http.auth.basic property to true:`quarkus.http.auth.basic=true`

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

When secure access is required, and no other authentication mechanisms are enabled, the built-in Basic authentication of Quarkus is the fallback authentication mechanism. Therefore, in this tutorial, you do not need to set the property quarkus.http.auth.basic to true.

  1. Configure at least one data source in the application.properties file so the quarkus-security-jpa extension can access your database. For example:======

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. To initialize the database with users and roles, implement the Startup class, as outlined in the following code snippet:

  • The URLs of Reactive datasources that are used by the quarkus-security-jpa-reactive extension are set with the quarkus.datasource.reactive.url configuration property and not the quarkus.datasource.jdbc.url configuration property typically used by JDBC datasources.[source, properties]

%prod.quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost:5431/security_jpa
  • In this tutorial, a PostgreSQL database is used for the identity store. Hibernate ORM automatically creates the database schema on startup. This approach is suitable for development but is not recommended for production. Therefore, adjustments are needed in a production environment.

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");
    }
}

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

The preceding example demonstrates how the application can be protected and identities provided by the specified database.

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

In a production environment, do not store plain text passwords. As a result, the quarkus-security-jpa defaults to using bcrypt-hashed passwords.

Test your application by using Dev Services for PostgreSQL

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

Complete the integration testing of your application in JVM and native modes by using Dev Services for PostgreSQL before you run your application in production mode.

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

Start by adding the following dependencies to your test project:

pom.xml
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.rest-assured:rest-assured")
  • To run your application in dev mode:

Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/dev.adoc[]

  • The following properties configuration demonstrates how to enable PostgreSQL testing to run only in production (prod) mode. In this scenario, Dev Services for PostgreSQL launches and configures a PostgreSQL test container.

%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
  • If you add the %prod. profile prefix, data source properties are not visible to Dev Services for PostgreSQL and are only observed by an application running in production mode.

  • To write the integration test, use the following code sample:

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"));
    }
}

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

As you can see in this code sample, you do not need to start the test container from the test code.

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

When you start your application in dev mode, Dev Services for PostgreSQL launches a PostgreSQL dev mode container so that you can start developing your application. While developing your application, you can add and run tests individually by using the Continuous Testing feature. Dev Services for PostgreSQL supports testing while you develop by providing a separate PostgreSQL test container that does not conflict with the dev mode container.

Use curl or a browser to test your application

  • Use the following example to start the PostgreSQL server:

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

  • Compile and run your Quarkus application by using one of the following methods:

    • JVM mode[style="arabic"]

      1. Compile the application:====== Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/build.adoc[]

  1. Run the application:======

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

    1. Compile the application:====== Unresolved directive in security-getting-started-tutorial.adoc - include::{includes}/devtools/build-native.adoc[]

  1. Run the application:======

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

Access and test the application security

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

When your application is running, you can access its endpoints by using one of the following Curl commands.

  • Connect to a protected endpoint anonymously:======

$ 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
  • Connect to a protected endpoint anonymously:======

$ 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
  • Connect to a protected endpoint as an authorized user:======

$ 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。

You can also access the same endpoint URLs by using a browser.

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

If you use a browser to connect to a protected resource anonymously, a Basic authentication form displays, prompting you to enter credentials.

=== Results

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

When you provide the credentials of an authorized user, for example, admin:admin, the Jakarta Persistence security extension authenticates and loads the user’s roles. The admin user is authorized to access the protected resources.

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

If a resource is protected with @RolesAllowed("user"), the user admin is not authorized to access the resource because it is not assigned to the "user" role, as shown in the following example:

$ 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 的用户被授权,并且安全上下文包含主体详细信息,例如用户名。

Finally, the user named user is authorized, and the security context contains the principal details, for example, the username.

$ 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 标识提供程序集成而实现的。

You have successfully learned how to create and test a secure Quarkus application. This was achieved by integrating the built-in Basic authentication in Quarkus with the Jakarta Persistence identity provider.

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

After completing this tutorial, you can explore more advanced security mechanisms in Quarkus. The following information shows you how to use OpenID Connect for secure single sign-on access to your Quarkus endpoints:

== References