Secure a Quarkus application with Basic authentication and Jakarta Persistence

Secure your Quarkus application endpoints by combining the built-in Quarkus Basic authentication with the Jakarta Persistence identity provider to enable role-based access control (RBAC). The Jakarta Persistence IdentityProvider creates a SecurityIdentity instance, which is used during user authentication to verify and authorize access requests making your Quarkus application secure.

Jakarta Persistence の詳細は、Quarkus Security と Jakarta Persistence ガイドを参照してください。

このチュートリアルでは、Quarkusでより高度なセキュリティメカニズムを実装するための準備、たとえば、OpenID Connect(OIDC)認証メカニズムの使用方法について説明します。

前提条件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.9.3

  • 使用したい場合は、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

何を構築するか

このチュートリアルでは、さまざまな認可ポリシーを示すために、次のエンドポイントを提供するアプリケーションを構築する手順を説明します:

エンドポイント 説明

/api/public

/api/public エンドポイントには匿名でアクセスすることができます。

/api/admin

The /api/admin endpoint is protected with role-based access control (RBAC). Only users granted with the admin role can access it. At this endpoint, the @RolesAllowed annotation enforces the access constraint declaratively.

/api/users/me

The /api/users/me endpoint is protected by RBAC. Only users that have the user role can access the endpoint. This endpoint returns the caller’s username as a string.

完了した例を確認するには、https://github.com/quarkusio/quarkus-quickstarts/archive/3.2.zip[アーカイブ] をダウンロードするか、Git リポジトリーのクローンを作成します。

git clone -b 3.2 https://github.com/quarkusio/quarkus-quickstarts.git

You can find the solution in the security-jpa-quickstart directory in the upstream {ProductName} community.

1. Maven プロジェクトの作成および検証

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

Hibernate ORM with Panache is used to store your user identities but you can also use Hibernate ORM with the security-jpa extension. Both Hibernate Reactive and Hibernate Reactive with Panache can be used with the security-jpa-reactive extension.

優先するデータベースコネクターライブラリーも追加する必要があります。 このサンプルチュートリアルの手順では、アイデンティティーストアに PostgreSQL データベースを使用します。

1.1. Maven プロジェクトの作成

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

  • Jakarta Persistence エクステンションで新しい Maven プロジェクトを作成するには、以下のいずれかの手順を実行します。

    • Hibernate ORM を使用して Maven プロジェクトを作成するには、次のコマンドを使用します。

      コマンドラインインタフェース
      quarkus create app org.acme:security-jpa-quickstart \
          --no-code
      cd security-jpa-quickstart

      Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

      Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

      Maven
      mvn io.quarkus.platform:quarkus-maven-plugin:3.2.12.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=security-jpa-quickstart \
          -DnoCode
      cd security-jpa-quickstart

      Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

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

      コマンドラインインタフェース
      quarkus create app org.acme:security-jpa-reactive-quickstart \
          --no-code
      cd security-jpa-reactive-quickstart

      Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

      Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

      Maven
      mvn io.quarkus.platform:quarkus-maven-plugin:3.2.12.Final:create \
          -DprojectGroupId=org.acme \
          -DprojectArtifactId=security-jpa-reactive-quickstart \
          -DnoCode
      cd security-jpa-reactive-quickstart

      Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

  • 既存の Maven プロジェクトに Jakarta Persistence エクステンションを追加するには、次のいずれかの手順を実行します:

    • Security Jakarta Persistence エクステンションを、Hibernate ORM を使用する既存の Maven プロジェクトに追加するには、プロジェクトのベース・ディレクトリーから以下のコマンドを実行します:

      CLI
      quarkus extension add 'security-jpa'
      Maven
      ./mvnw quarkus:add-extension -Dextensions='security-jpa'
      Gradle
      ./gradlew addExtension --extensions='security-jpa'
    • Hibernate Reactive を使用する既存の Maven プロジェクトに Security Jakarta Persistence エクステンションを追加するには、プロジェクトのベース・ディレクトリーから以下のコマンドを実行します:

      CLI
      quarkus extension add 'security-jpa-reactive'
      Maven
      ./mvnw quarkus:add-extension -Dextensions='security-jpa-reactive'
      Gradle
      ./gradlew addExtension --extensions='security-jpa-reactive'

1.2. quarkus-security-jpa 依存関係の確認

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

  • To verify the 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 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")

2. アプリケーションの記述

  • 次のいずれかの方法を使用して、API エンドポイントを保護し、アプリケーションにアクセスできるユーザーを決定します。

    • すべてのユーザーがアプリケーションにアクセスできるように、 /api/public エンドポイントを実装します。 次のコードスニペットに示すように、通常の Jakarta REST リソースを Java ソースコードに追加します。

      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 the /api/public endpoint to allow all users to access the application. 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";
          }
      }
    • 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();
          }
      }

2.1. ユーザーエンティティーの定義

  • 次のコードで説明するように、 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 エンティティまたは Panache 付き Hibernate ORM エンティティのいずれかにすることができます。
2 ユーザー名に使用するフィールドを示します。
3 パスワードに使用するフィールドを示します。 デフォルトでは、bcrypt でハッシュされたパスワードを使用します。 プレーンテキストまたはカスタムパスワードを使用するように設定できます。
4 ターゲットプリンシパル表現属性に追加されたロールのコンマ区切りリストを示します。
5 適切な bcrypt ハッシュを使用してパスワードをハッシュ化して、ユーザーを追加できます。
Hibernate Reactive Panache uses io.quarkus.hibernate.reactive.panache.PanacheEntity instead of io.quarkus.hibernate.orm.panache.PanacheEntity. For more information, see User file.

2.2. アプリケーションの設定

  1. quarkus.http.auth.basic プロパティーを true に設定して、組み込みの Quarkus Basic 認証 メカニズムを有効にします。

    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.

  2. Configure at least one data source in the application.properties file so the 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
  3. データベースをユーザーとロールで初期化するには、次のコードで説明するように Startup クラスを実装します:

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

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

上記の例は、指定されたデータベースによってアプリケーションがどのように保護されて ID が提供されるかを示しています。

本番環境では、プレーンテキストのパスワードは保存しないでください。 そのため、 security-jpa はデフォルトで bcrypt でハッシュ化したパスワードを使用します。

3. Test your application by using Dev Services for PostgreSQL

アプリケーションを本番モードで実行する前に、Dev Services for PostgreSQL を使用して、JVM モードとネイティブモードでアプリケーションの結合テストを完了します。

  • アプリケーションを dev モードで実行するには、以下を実行します。

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

このコードサンプルからわかるように、テストコードからテストコンテナーを起動する必要はありません。

When you start your application in dev mode, Dev Services for PostgreSQL launches a PostgreSQL devmode container so that you can start developing your application. While developing your application, you can add tests one by one and run them 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 devmode container.

3.1. 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

3.2. アプリケーションのコンパイルと実行

  • 次のいずれかの方法で、Quarkusアプリケーションをコンパイルして実行します:

    • JVMモード

      1. アプリケーションをコンパイルします。

        CLI
        quarkus build
        Maven
        ./mvnw install
        Gradle
        ./gradlew build
      2. アプリケーションを実行します。

        java -jar target/quarkus-app/quarkus-run.jar
    • ネイティブモード

      1. アプリケーションをコンパイルします。

        CLI
        quarkus build --native
        Maven
        ./mvnw install -Dnative
        Gradle
        ./gradlew build -Dquarkus.package.type=native
      2. アプリケーションを実行します。

        ./target/security-jpa-quickstart-runner

3.3. アプリケーションのセキュリティへのアクセスおよびテスト

アプリケーションの実行中に、以下の 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 にアクセスすることもできます。

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

3.4. 結果

認可されたユーザーのクレデンシャル(例えば、 admin:admin )を提供すると、Jakarta Persistenceセキュリティエクステンションは、認証してそのユーザーのロールをロードします。 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 shell 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

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

Congratulations! You have learned how to create and test a secure Quarkus application by combining the built-in Basic authentication in Quarkus with the Jakarta Persistence identity provider.

このチュートリアルの完了後、Quarkus のより高度なセキュリティーメカニズムについて学習できます。 以下の情報は、Quarkus エンドポイントへの安全なシングルサインオンアクセスに OpenID Connect を使用する方法を示しています。