The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

Quarkus セキュリティー入門

このガイドでは、Basic AuthenticationMechanismJPA IdentityProvider を使用して、アプリケーションへのアクセスを許可する前に Role Based Access Control (RBAC) レイヤーで認可される SecurityIdentity を作成して、セキュアな Quarkus アプリケーションを迅速に作成する方法を説明します。

Basic AuthenticationMechanismJPA IdentityProvider の組み合わせは、application.properties でユーザーやロールを設定するよりも優れているため、このガイドでは Basic AuthenticationMechanismJPA IdentityProvider を組み合わせて使用することを推奨しています。JPA IdentityProvider に相当するリアクティブが導入され次第、推奨内容を更新する予定です。

このガイドの最後では、Quarkus セキュリティー、特にその OpenId Connect Authentication Mechanism についてさらに学ぶ方法を推奨しています。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.8.1+

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

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

アーキテクチャ

この例では、3つのエンドポイントを提供する非常にシンプルなマイクロサービスを構築します:

  • /api/public

  • /api/users/me

  • /api/admin

/api/public エンドポイントは匿名でアクセスできます。 /api/admin エンドポイントは RBAC (Role-Based Access Control) で保護されており、 admin の役割を与えられたユーザーのみがアクセスできます。このエンドポイントでは、 @RolesAllowed アノテーションを使用して、アクセス制約を宣言的に強制します。 /api/users/me エンドポイントも RBAC (Role-Based Access Control) で保護されており、 user ロールで付与されたユーザーのみがアクセスできます。レスポンスとして、ユーザーに関する詳細を含むJSONドキュメントを返します。

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

Gitリポジトリをクローンする: git clone https://github.com/quarkusio/quarkus-quickstarts.git 、または アーカイブ をダウンロードします。

ソリューションは security-jpa-quickstart ディレクトリー にあります。

Maven プロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

CLI
quarkus create app org.acme:security-jpa-quickstart \
    --extension=security-jpa,jdbc-postgresql,resteasy-reactive,hibernate-orm-panache \
    --no-code
cd security-jpa-quickstart

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

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.12.3.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=security-jpa-quickstart \
    -Dextensions="security-jpa,jdbc-postgresql,resteasy-reactive,hibernate-orm-panache" \
    -DnoCode
cd security-jpa-quickstart

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

このコマンドは、セキュリティー ソースを JPA エンティティーにマップできる security-jpa エクステンションをインポートして、Maven プロジェクトを生成します。

ユーザー ID の保存には Hibernate ORM with Panache が使用されていますが、Hibernate ORM を使用することも可能です。

選択したデータベースコネクタライブラリを追加することを忘れないでください。ここでは、PostgreSQLをIDストアとして使用しています。

すでにQuarkusプロジェクトが設定されている場合は、プロジェクトのベースディレクトリーで以下のコマンドを実行することで、プロジェクトに security-jpa エクステンションを追加することができます:

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

これにより、 pom.xml に以下が追加されます:

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

アプリケーションの記述

/api/public エンドポイントの実装から始めましょう。以下のソースコードから分かるように、通常のJAX-RSリソースです:

package org.acme.security.jpa;

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

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

    @GET
    @PermitAll
    @Produces(MediaType.TEXT_PLAIN)
    public String publicResource() {
        return "public";
   }
}

/api/admin エンドポイントのソースコードも非常にシンプルです。ここでの主な違いは、 admin ロールで付与されたユーザーだけがエンドポイントにアクセスできるように @RolesAllowed アノテーションを使用していることです:

package org.acme.security.jpa;

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

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

    @GET
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String adminResource() {
         return "admin";
    }
}

最後に、 /api/users/me エンドポイントを考えてみましょう。下のソースコードを見ればわかるように、 user の役割を持つユーザのみを信頼しています。現在認証されているプリンシパルへのアクセスを得るために SecurityContext を使用しており、ユーザーの名前を返します。この情報はデータベースから読み込まれます。

package org.acme.security.jpa;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.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();
    }
}

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

これで、 User エンティティーにいくつかのアノテーションを追加することで、セキュリティー情報がモデルにどのように保存されているかを説明することができます:

package org.acme.security.jpa;

import javax.persistence.Entity;
import javax.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 in 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 このアノテーションは、単一のエンティティーに存在しなければなりません。この例のように、通常のHibernate ORMエンティティーまたはHibernate ORM with Panacheエンティティーにすることができます。
2 これは、ユーザー名に使用されるフィールドを示します。
3 これは、パスワードに使用するフィールドを示します。これはデフォルトでは bcrypt ハッシュ化されたパスワードを使用するように設定されていますが、クリアテキストパスワードやカスタムパスワード用に設定することもできます。
4 これは、対象のプリンシパル表現属性に追加されたロールのコンマ区切りリストを示します。
5 この方法では、パスワードを適切なbcryptハッシュでハッシュしながらユーザーを追加することができます。

アプリケーションの設定

まず、ベーシック HTTP 認証メカニズムquarkus.http.auth.basic=true で有効化する必要があります。ただし、このデモでは、セキュアなアクセスが必要な場合に他の認証メカニズムが有効になっていなければ Basic HTTPAuthenticationMechanism が使用されるため、quarkus.http.auth.basic=true を設定する必要はありません。

次に、データソースを設定します。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

私たちのコンテキストでは、IDストアとしてPostgreSQLを使用しています。データベーススキーマはHibernate ORMによって起動時に自動的に作成され(本番ではこれを変更します)、 Startup クラスのユーザーとロールでデータベースを初期化します。

package org.acme.security.jpa;

import javax.enterprise.event.Observes;
import javax.inject.Singleton;
import javax.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 でハッシュ化されたパスワードを使用するようになりました。

アプリケーションのテスト

Dev Services for PostgreSQL を使用する場合

コマンドラインインタフェース
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

アプリケーションを実稼働モードで動作させる前に、統合テストを追加してみましょう。

JVM とネイティブモードでのアプリケーションの統合テストには、Dev Services for PostgreSQL の使用をお勧めします。Dev Services for PostgreSQL は、PostgreSQL の設定プロパティーが実稼働 (prod) モードでのみ有効な場合に、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"));
    }
}

見てわかるとおり、テストコードからテストコンテナーを起動する必要はありません。

アプリケーションを devmode で起動すると、Dev Services for PostgreSQLPostgreSQL devmode コンテナーを起動するため、ユーザーはアプリケーションの開発に集中できます。継続的テスト 機能を使用すると、開発中にテストを 1 つずつ追加して実行することも可能です。Dev Services for PostgreSQL は、別の PostgreSQL テストコンテナーでこれらのテストをサポートするため、devmode コンテナーと競合しません。

Curl またはブラウザーを使用する場合

まず、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

次に、JVM または Native モードでアプリケーションをコンパイルし、実行します。

JVM モード

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

CLI
quarkus build
Maven
./mvnw clean package
Gradle
./gradlew build

次に、それを実行します:

java -jar target/quarkus-app/quarkus-run.jar

Native モード

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

CLI
quarkus build --native
Maven
./mvnw package -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

それを実行します:

./target/security-jpa-quickstart-runner

これで curl やお気に入りのブラウザーでテストできます。ここでは curl を使用しますが、ブラウザーから同じエンドポイント URL にアクセスすることもできます。

まず最初に確認すべきは、匿名でのアクセスが可能かどうかということです。

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

admin:admin 資格情報を提供することで、エクステンションはユーザーを認証し、そのロールをロードしました。 admin ユーザーは、保護されたリソースへのアクセスを許可されています。

ユーザー admin は、この役割を持っていないので、 @RolesAllowed("user") で保護されたリソースへのアクセスを禁止する必要があります。

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

セキュリティー JPA リファレンスガイド

これでデモの実行とテストが完了ました。次に、JPA アイデンティティーストアの準備を詳しく説明します。

サポートされているモデルの種類

  • @UserDefinition クラスは JPA エンティティーである必要があります(Panache を使用しているかどうかは問いません)。

  • @Username@Password フィールドの型は String でなければなりません。

  • @Roles フィールドは StringCollection<String>Collection<X> (X@RolesValue アノテーションが付与された String フィールドを 1 つ持つエンティティークラス) のいずれかとします。

  • String role 要素の型は、カンマで区切られたロールのリストとして解析されます。

別のエンティティーにロールを格納する

また、別のエンティティーにロールを格納することもできます。

@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;
}

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

デフォルトでは、パスワードは MCF ( Modular Crypt Format) の下で bcryptでハッシュ化されて保存されると考えています。

このようなハッシュ化されたパスワードを作成する必要がある場合は、便利な String BcryptUtil.bcryptHash(String password) 関数を用意しています。デフォルトでは、ランダムなソルトを作成して 10 回の繰り返しでハッシュ化します (繰り返しとソルトも指定できます)。

MCF を使うと、ハッシュアルゴリズムや反復回数、 ソルトを格納するための専用のカラムは必要ありません。

また、異なるハッシュアルゴリズム( @Password(value = PasswordType.CUSTOM, provider = CustomPasswordProvider.class) )を使用してパスワードを保存することもできます。

@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 pass) {
        byte[] digest = DatatypeConverter.parseHexBinary(pass);
        return SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256, digest);
    }
}

警告: @Password(PasswordType.CLEAR) を使ってパスワードをクリアテキストで保存することもできますが、本番では絶対にしないことを強くお勧めします。

次のステップ

ここでは、ベーシック HTTP 認証メカニズムJPA IdentityProvider を使用して、セキュアな Quarkus アプリケーションを作成およびテストする方法を学びました。

次のステップとして、OpenId Connect を使用して Quarkus エンドポイントにセキュアなシングルサインオンアクセスを提供する方法を確認することをお勧めします。Quarkus - Using OpenID Connect を使用した、ベアラートークン認可によるサービスアプリケーションの保護 および Quarkus - OpenID Connect を使用した、認可コードフローによる Web アプリケーションの保護 ガイドを確認してください。

Quarkus セキュリティーの詳細については、Quarkus セキュリティー ドキュメントを参照してください。