The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

ウェブエンドポイントの認可

Quarkus には、プラグ可能な Web セキュリティーレイヤーが組み込まれています。 セキュリティーがアクティブな場合、システムはすべての HTTP リクエストに対して権限チェックを実行し、続行するかどうかを決定します。

quarkus.http.auth. 設定によってパスが制限されている場合、@PermitAll を使用してもパスは開きません。 特定のパスにアクセスできるようにするには、Quarkus のセキュリティー設定内で適切な設定を行う必要があります。

Jakarta RESTful Web サービスを使用する場合は、HTTP パスレベルのマッチングではなく、quarkus.security.jaxrs.deny-unannotated-endpoints または quarkus.security.jaxrs.default-roles-allowed を使用して、デフォルトのセキュリティー要件を設定することを検討してください。アノテーションによって、個々のエンドポイントでこれらのプロパティーをオーバーライドできるためです。

認可は、セキュリティ・プロバイダーが提供するユーザー・ロールに基づきます。これらのロールをカスタマイズするには、 SecurityIdentityAugmentor を作成することができます。 セキュリティ・アイデンティティのカスタマイズ を参照してください。

設定を利用した認可

権限は、Quarkus 設定で権限セットによって定義されます。各権限セットで、アクセス制御用のポリシーを指定します。

Table 1. Quarkus ポリシーの概要
組込ポリシー Description

deny

このポリシーは、すべてのユーザーを拒否します。

permit

このポリシーは、すべてのユーザーを許可します。

authenticated

このポリシーは、認証されたユーザーのみを許可します。

特定のロールを持つユーザーにリソースへのアクセスを許可するロールベースのポリシーを定義できます。

ロールベースポリシーの例
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin                  (1)
1 これは、user ロールと admin ロールを持つユーザーを許可するロールベースのポリシーを定義します。

次の設定例に示すように、application.properties ファイルで定義されている組み込みの権限セットを設定することで、カスタムポリシーを参照できます。

ポリシーの設定例
quarkus.http.auth.permission.permit1.paths=/public/*                            (1)
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET

quarkus.http.auth.permission.deny1.paths=/forbidden                             (2)
quarkus.http.auth.permission.deny1.policy=deny

quarkus.http.auth.permission.roles1.paths=/roles-secured/*,/other/*,/api/*      (3)
quarkus.http.auth.permission.roles1.policy=role-policy1
1 この権限は、デフォルトの組み込み permit ポリシーを参照して、/public への GET メソッドを許可します。 この場合、このリクエストはいずれにせよ許可されるため、示されている設定によるこの例への影響はありません。
2 この権限は、/forbidden パスと /forbidden/ パスの両方に対して組み込みの deny ポリシーを参照します。 これは * で終わっていないため、パスが完マッチします。
3 この権限セットは、以前に定義されたポリシーを参照します。 roles1 は例の名前です。権限セットには任意の名前を付けることができます。

上記の例の正確なパスパターン /forbidden は、/forbidden/ パスも保護します。 このようにして、以下の例の forbidden エンドポイントは deny1 権限によって保護されます。

package org.acme.crud;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/forbidden")
public class ForbiddenResource {
    @GET
    public String forbidden() { (1)
        return "No!";
    }
}
1 forbidden エンドポイントを保護するには、/forbidden パスと /forbidden/ パスの両方を保護する必要があります。

/forbidden/ パスへのアクセスを許可する必要がある場合は、以下の例のように、より具体的な正確なパスを使用して新しい権限を追加してください。

quarkus.http.auth.permission.permit1.paths=/forbidden/ (1)
quarkus.http.auth.permission.permit1.policy=permit
1 /forbidden/ パスは保護されていません。

カスタムの HttpSecurityPolicy

独自の名前付きポリシーを登録すると便利な場合があります。以下の例のように、 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy インターフェイスを実装するアプリケーションスコープの CDI Bean を作成することでこれを実現できます。

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomNamedHttpSecPolicy implements HttpSecurityPolicy {
    @Override
    public Uni<CheckResult> checkPermission(RoutingContext event, Uni<SecurityIdentity> identity,
            AuthorizationRequestContext requestContext) {
        if (customRequestAuthorization(event)) {
            return Uni.createFrom().item(CheckResult.PERMIT);
        }
        return Uni.createFrom().item(CheckResult.DENY);
    }

    @Override
    public String name() {
        return "custom"; (1)
    }

    private static boolean customRequestAuthorization(RoutingContext event) {
        // here comes your own security check
        return !event.request().path().endsWith("denied");
    }
}
1 名前付き HTTP セキュリティーポリシーは、application.properties パスマッチングルールにマッチするリクエストにのみ適用されます。
設定ファイルから参照されるカスタム名の HttpSecurityPolicy の例
quarkus.http.auth.permission.custom1.paths=/custom/*
quarkus.http.auth.permission.custom1.policy=custom                              (1)
1 カスタムポリシー名は、io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name メソッドによって返される値とマッチする必要があります。

Alternatively, you can bind custom named HttpSecurityPolicy to Jakarta REST endpoints with the @AuthorizationPolicy security annotation.

Example of custom named HttpSecurityPolicy bound to a Jakarta REST endpoint
import io.quarkus.vertx.http.security.AuthorizationPolicy;
import jakarta.annotation.security.DenyAll;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@DenyAll (1)
@Path("hello")
public class HelloResource {

    @AuthorizationPolicy(name = "custom") (2)
    @GET
    public String hello() {
        return "hello";
    }

}
1 The @AuthorizationPolicy annotation can be used together with other standard security annotations. As usual, the method-level annotation has priority over class-level annotation.
2 Apply custom named HttpSecurityPolicy to the Jakarta REST hello endpoint.

すべてのリクエストで呼び出されるグローバル HttpSecurityPolicy を作成することもできます。 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name メソッドを実装せず、ポリシーに名前を付けないままにします。

パスとメソッドのマッチング

権限セットでは、パスとメソッドをコンマ区切りのリストとして指定することもできます。 パスが * ワイルドカードで終わる場合、生成されるクエリーはすべてのサブパスにマッチします。 それ以外の場合は、完全一致を照会し、特定のパスにのみマッチします。

quarkus.http.auth.permission.permit1.paths=/public*,/css/*,/js/*,/robots.txt    (1)
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD
1 パスの末尾にある * ワイルドカードは、0 個以上のパスセグメントにマッチしますが、/public パスから始まる単語にはマッチしません。 そのため、/public-info のようなパスはこのパターンとはマッチしません。

パスはマッチするがメソッドはマッチしない場合

パスに基づいて 1 つ以上の権限セットにマッチしても、必要なメソッドのいずれにもマッチしない場合、リクエストは拒否されます。

上記の権限セットが与えられている場合、GET/public/foo はパスとメソッドの両方にマッチするため、許可されます。 対照的に、POST/public/foo はパスにマッチしますが、メソッドにはマッチしないため、拒否されます。

複数のパスのマッチング:一番長いパスが勝ちます。

マッチングは常に「最長パスが優先される」という基準で行われます。 具体的な権限セットがマッチした場合、それよりも具体性に欠ける権限セットは考慮されません。

quarkus.http.auth.permission.permit1.paths=/public/*
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

quarkus.http.auth.permission.deny1.paths=/public/forbidden-folder/*
quarkus.http.auth.permission.deny1.policy=deny

上記の権限セットの場合、GET/public/forbidden-folder/foo は両方の権限セットのパスとマッチします。 ただし、より長いパスが deny1 権限セットのパスとマッチするため、deny1 が選択され、リクエストは拒否されます。

前述の deny1 権限と permit1 権限の例で示したように、サブパスの権限がルートパスの権限よりも優先されます。

このルールをさらに例示するために、サブパス権限ではパブリックリソースへのアクセスを許可し、ルートパス権限では認可を要求するシナリオを示します。

quarkus.http.auth.policy.user-policy.roles-allowed=user
quarkus.http.auth.permission.roles.paths=/api/*
quarkus.http.auth.permission.roles.policy=user-policy

quarkus.http.auth.permission.public.paths=/api/noauth/*
quarkus.http.auth.permission.public.policy=permit

複数のサブパスのマッチング: * ワイルドカードへの最長パスが優先される

前の例では、パスの末尾が * ワイルドカードの場合に、すべてのサブパスがマッチすることを 示しました。

This wildcard also applies in the middle of a path, representing a single path segment. It cannot be mixed with other path segment characters; thus, path separators always enclose the * wildcard, as seen in the /public/*/about-us path.

複数のパスパターンが同じリクエストパスに対応する場合、システムは * ワイルドカードにつながる最長のサブパスを選択します。 この文脈では、すべてのパスセグメント文字は、* ワイルドカード よりも具体的になります。

簡単な例を挙げてみます。

quarkus.http.auth.permission.secured.paths=/api/*/detail                    (1)
quarkus.http.auth.permission.secured.policy=authenticated
quarkus.http.auth.permission.public.paths=/api/public-product/detail        (2)
quarkus.http.auth.permission.public.policy=permit
1 /api/product/detail などのリクエストパスには、認証済みユーザーのみがアクセスできます。
2 パス /api/public-product/detail はより詳細であるため、どのユーザーもアクセスできます。

設定を使用した認可で保護されたすべてのパスをテストする必要があります。 複数のワイルドカードを使用してパスパターンを記述するのは面倒な場合があります。 パスが意図したとおりに認可されていることを確認してください。

次の例では、パスは最も詳細なものから最も詳細でないものの順に並べられています。

最も詳細なパスから最も詳細でないパスの順に並べたリクエストパス /one/two/three/four/five のマッチ
/one/two/three/four/five
/one/two/three/four/*
/one/two/three/*/five
/one/two/three/*/*
/one/two/*/four/five
/one/*/three/four/five
/*/two/three/four/five
/*/two/three/*/five
/*

パスの末尾にある * ワイルドカードは、0 個以上のパスセグメントにマッチします。 他の場所に配置された * ワイルドカードは、1 つのパスセグメントのみにマッチします。

複数のパスのマッチング:一番具体的なパスが勝ちます

パスが複数の権限セットに登録されている場合、リクエストにマッチする HTTP メソッドを明示的に指定する権限セットが優先されます。 この場合、メソッドのない権限セットは、リクエストメソッドがメソッド仕様を持つ権限セットとマッチしない場合にのみ、有効になります。

quarkus.http.auth.permission.permit1.paths=/public/*
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

quarkus.http.auth.permission.deny1.paths=/public/*
quarkus.http.auth.permission.deny1.policy=deny

上記の権限セットは、GET /public/foo が両方の権限セットのパスにマッチすることを示しています。ただし、これは permit1 権限セットの明示的なメソッドと明確に一致しています。したがって、permit1 が選択され、リクエストが受け入れられます。

一方、PUT/public/foopermit1 のメソッド権限とマッチしません。その結果、deny1 がアクティブになり、リクエストが拒否されます。

複数のパスのマッチング:一番長いパスが勝ちます

場合によっては、前述のルールにより、複数の権限セットが同時に適用されることがあります。 その場合、リクエストを続行するには、すべての権限でアクセスが許可されている必要があります。 そのためには、両方にメソッドが指定されているか、メソッドが存在しない必要があります。 メソッド固有のマッチが優先されます。

quarkus.http.auth.policy.user-policy1.roles-allowed=user
quarkus.http.auth.policy.admin-policy1.roles-allowed=admin

quarkus.http.auth.permission.roles1.paths=/api/*,/restricted/*
quarkus.http.auth.permission.roles1.policy=user-policy1

quarkus.http.auth.permission.roles2.paths=/api/*,/admin/*
quarkus.http.auth.permission.roles2.policy=admin-policy1
上記の権限セットの場合、GET /api/foo は両方の権限セットのパスにマッチするため、useradmin の両方のロールが必要です。

アクセスを拒否するための設定プロパティ

以下の設定により、ロールベースアクセスコントロール(RBAC)の拒否動作が変更されます:

quarkus.security.jaxrs.deny-unannotated-endpoints=true|false

true に設定すると、すべての Jakarta REST エンドポイントへのアクセスがデフォルトで拒否されます。 Jakarta REST エンドポイントにセキュリティーアノテーションがない場合、デフォルトで @DenyAll 動作になります。 これにより、保護されているはずのエンドポイントが誤って公開されることを回避できます。 デフォルトは false です。

quarkus.security.jaxrs.default-roles-allowed=role1,role2

アノテーションのないエンドポイントのデフォルトのロール要件を定義します。 ** ロールは、認証されたすべてのユーザーを意味する特別なロールです。 代わりに deny が有効になるため、これを deny-unannotated-endpoints と組み合わせることはできません。

quarkus.security.deny-unannotated-members=true|false

true に設定すると、セキュリティーアノテーションを持たないが、セキュリティーアノテーションを持つメソッドを含むクラスで定義されているすべての CDI メソッドと Jakarta REST エンドポイントへのアクセスが拒否されます。 デフォルトは false です。

権限の無効化

権限は、宣言された各権限の enabled プロパティを使って、ビルド時に次のように無効にすることができます:

quarkus.http.auth.permission.permit1.enabled=false
quarkus.http.auth.permission.permit1.paths=/public/*,/css/*,/js/*,/robots.txt
quarkus.http.auth.permission.permit1.policy=permit
quarkus.http.auth.permission.permit1.methods=GET,HEAD

権限は、システムプロパティや環境変数を使って実行時に再有効化することができます(例えば、以下のように): -Dquarkus.http.auth.permission.permit1.enabled=true .

権限パスとHTTPルートパス

quarkus.http.root-path 設定プロパティは、 http endpoint context path を変更します。

デフォルトでは、設定された権限のパスの前に自動的に quarkus.http.root-path 、フォワードスラッシュを使用しない場合などです。 :

quarkus.http.auth.permission.permit1.paths=public/*,css/*,js/*,robots.txt

この構成は以下に相当します。 :

quarkus.http.auth.permission.permit1.paths=${quarkus.http.root-path}/public/*,${quarkus.http.root-path}/css/*,${quarkus.http.root-path}/js/*,${quarkus.http.root-path}/robots.txt

先頭のスラッシュは、設定された権限パスの解釈方法を変更します。 設定された URL はそのまま使用され、quarkus.http.root-path の値が変更されてもパスは調整されません。

以下に例を示します。
quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt

この設定は、固定または静的 URL /public から提供されるリソースにのみ影響します。この URL は、quarkus.http.root-path/ 以外に設定されていると、アプリケーションリソースとマッチしない可能性があります。

詳細は、Path Resolution in Quarkus を参照してください。

SecurityIdentity ロールのマッピング

優先されるロールベースのポリシーは、SecurityIdentity ロールをデプロイメント固有のロールにマッピングできます。 これらのロールは、@RolesAllowed アノテーションを使用してエンドポイント認可に適用できます。

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 (1)
quarkus.http.auth.permission.roles1.paths=/* (2)
quarkus.http.auth.permission.roles1.policy=admin-policy1
1 admin ロールを Admin1 ロールにマップします。SecurityIdentity には admin ロールと Admin1 ロールの両方が存在します。
2 /* パスは保護されており、認証された HTTP リクエストのみにアクセスが許可されます。

パスに関係なく、SecurityIdentity ロールをデプロイメント固有のロールにマッピングするだけの場合は、次のようにすることもできます。

quarkus.http.auth.roles-mapping.admin=Admin1 (1) (2)
1 admin ロールを Admin1 ロールにマップします。SecurityIdentity には admin ロールと Admin1 ロールの両方が存在します。
2 /* パスは保護されていません。標準のセキュリティーアノテーションを使用してエンドポイントを保護するか、この設定プロパティーに加えて HTTP 権限を定義する必要があります。

共有権限チェック

非共有権限チェックの重要なルールの 1 つは、最も具体的なパスマッチが 1 つだけ適用されることです。 当然、同じ優先パスを持つ権限を必要なだけ指定することができ、それらはすべて適用されます。 ただし、何度も繰り返さずに多くのパスに適用する権限チェックが必要になる場合があります。 そこで共有権限チェックを使用します。共有権限チェックは、権限パスがマッチしたときに常に適用されます。

すべての HTTP リクエストに適用されるカスタム名の HttpSecurityPolicy の例
quarkus.http.auth.permission.custom1.paths=/*
quarkus.http.auth.permission.custom1.shared=true    (1)
quarkus.http.auth.permission.custom1.policy=custom

quarkus.http.auth.policy.admin-policy1.roles-allowed=admin
quarkus.http.auth.permission.roles1.paths=/admin/*
quarkus.http.auth.permission.roles1.policy=admin-policy1
1 カスタムの HttpSecurityPolicy は、admin-policy1 ポリシーとともに /admin/1 パスにも適用されます。
共有権限チェックを多数設定することは、共有されていない権限チェックを設定する場合よりも効果が低くなります。 以下の例のように、共有権限を使用して非共有権限チェックを補完します。
共有権限を持つ SecurityIdentity ロールのマッピング
quarkus.http.auth.policy.role-policy1.roles.root=admin,user (1)
quarkus.http.auth.permission.roles1.paths=/secured/*        (2)
quarkus.http.auth.permission.roles1.policy=role-policy1
quarkus.http.auth.permission.roles1.shared=true

quarkus.http.auth.policy.role-policy2.roles-allowed=user    (3)
quarkus.http.auth.permission.roles2.paths=/secured/user/*
quarkus.http.auth.permission.roles2.policy=role-policy2

quarkus.http.auth.policy.role-policy3.roles-allowed=admin
quarkus.http.auth.permission.roles3.paths=/secured/admin/*
quarkus.http.auth.permission.roles3.policy=role-policy3
1 ロール root は、/secured/user/* および /secured/admin/* パスにアクセスできるようになります。
2 /secured/* パスには、認証されたユーザーのみがアクセスできます。このようにして、/secured/all パスなどが保護されます。
3 共有権限は常に非共有権限の前に適用されるため、root ロールを持つ SecurityIdentity`には `user ロールも付与されます。

アノテーションを使った認可

Quarkus には、REST エンドポイントと CDI Bean 上の共通セキュリティーアノテーション @RolesAllowed@DenyAll@PermitAll に基づく Role-Based Access Control (RBAC) を可能にするセキュリティーが組み込まれています。

Table 2. Quarkus アノテーションの種類の概要
アノテーション型 Description

@DenyAll

どのセキュリティロールも指定されたメソッドを呼び出すことを許可されていないことを指定します。

@PermitAll

すべてのセキュリティロールが指定されたメソッドを呼び出すことを許可されるように指定します。

@PermitAll は認証なしでも誰でもアクセスできるようにします。

@RolesAllowed

Specifies the list of security roles allowed to access methods in an application.

@Authenticated

Quarkus provides the io.quarkus.security.Authenticated annotation that permits any authenticated user to access the resource. It’s equivalent to @RolesAllowed("**").

@PermissionsAllowed

Specifies the list of permissions that are allowed to invoke the specified methods.

@AuthorizationPolicy

Specifies named io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy that should authorize access to the specified endpoints.HttpSecurityPolicy. Named HttpSecurityPolicy can be used for general authorization checks as demonstrated by Example of custom named HttpSecurityPolicy bound to a Jakarta REST endpoint.

次の SubjectExposingResourceの例 は、エンドポイントを記述および保護するために Jakarta REST と Common Security アノテーションの両方を使用するエンドポイントを示します。

SubjectExposingResourceの例
import java.security.Principal;

import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;

@Path("subject")
public class SubjectExposingResource {

    @GET
    @Path("secured")
    @RolesAllowed("Tester") (1)
    public String getSubjectSecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); (2)
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("authenticated")
    @Authenticated (3)
    public String getSubjectAuthenticated(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("unsecured")
    @PermitAll (4)
    public String getSubjectUnsecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); (5)
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("denied")
    @DenyAll (6)
    public String getSubjectDenied(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }
}
1 /subject/secured エンドポイントは、 @RolesAllowed("Tester") アノテーションを使用して"Tester"というロールを付与された認証済みユーザーが必要です。
2 エンドポイントは、Jakarta REST SecurityContext からユーザープリンシパルを取得します。 これは、保護されたエンドポイントに対して non-null を返します。
3 The /subject/authenticated endpoint allows any authenticated user by specifying the @Authenticated annotation.
4 /subject/unsecured エンドポイントでは、 @PermitAll アノテーションを指定することで、認証されていないアクセスが可能になります。
5 ユーザープリンシパルを取得するためのこの呼び出しは、呼び出し元が認証されていない場合は null を返し、呼び出し元が認証されている場合は non-null を返します。
6 /subject/denied エンドポイントは @DenyAll アノテーションを宣言し、ユーザーが呼び出すかどうかに関係なく、REST メソッドとしてのすべての直接アクセスを禁止します。 このメソッドは、このクラスの他のメソッドによって引き続き内部的に呼び出すことができます。
IO スレッドで標準のセキュリティーアノテーションを使用する予定の場合は、Proactive Authentication の情報を確認してください。

@RolesAllowed アノテーション値は、デフォルト値とネストされたプロパティー式を含む property expressions をサポートします。 アノテーションで使用される設定プロパティーはランタイム時に解決されます。

Table 3. アノテーション値の例
アノテーション 値の説明

@RolesAllowed("${admin-role}")

エンドポイントは、admin-role プロパティーの値によって示されるロールを持つユーザーを許可します。

@RolesAllowed("${tester.group}-${tester.role}")

値が複数の変数を含むことができることを示す例。

@RolesAllowed("${customer:User}")

デフォルト値のデモンストレーション。 必要なロールは、customer プロパティーの値によって示されます。 ただし、そのプロパティーが指定されていない場合は、デフォルトで User という名前のロールが必要になります。

@RolesAllowed アノテーションにおけるプロパティ式の使用例
admin=Administrator
tester.group=Software
tester.role=Tester
%prod.secured=User
%dev.secured=**
all-roles=Administrator,Software,Tester,User
サブジェクトアクセス制御の例
import java.security.Principal;

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

@Path("subject")
public class SubjectExposingResource {

    @GET
    @Path("admin")
    @RolesAllowed("${admin}") (1)
    public String getSubjectSecuredAdmin(@Context SecurityContext sec) {
        return getUsername(sec);
    }

    @GET
    @Path("software-tester")
    @RolesAllowed("${tester.group}-${tester.role}") (2)
    public String getSubjectSoftwareTester(@Context SecurityContext sec) {
        return getUsername(sec);
    }

    @GET
    @Path("user")
    @RolesAllowed("${customer:User}") (3)
    public String getSubjectUser(@Context SecurityContext sec) {
        return getUsername(sec);
    }

    @GET
    @Path("secured")
    @RolesAllowed("${secured}") (4)
    public String getSubjectSecured(@Context SecurityContext sec) {
        return getUsername(sec);
    }

    @GET
    @Path("list")
    @RolesAllowed("${all-roles}") (5)
    public String getSubjectList(@Context SecurityContext sec) {
        return getUsername(sec);
    }

    private String getUsername(SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }
}
1 @RolesAllowed アノテーション値は、 Administrator の値に設定されています。
2 この /subject/software-tester エンドポイントには、"Software-Tester" のロールが付与された認証済みユーザーが必要です。 ロール定義では複数の式を使用できます。
3 この /subject/user エンドポイントには、@RolesAllowed("${customer:User}") アノテーションを使用して "User" ロールが付与された認証済みユーザーが必要です。これは、設定プロパティー customer を設定しなかったためです。
4 実稼働環境では、この /subject/secured エンドポイントには User ロールを持つ認証済みユーザーが必要です。 開発モードでは、認証されたすべてのユーザーが許可されます。
5 プロパティー式 all-roles は、コレクション型 List として扱われます。そのため、このエンドポイントには、AdministratorSoftwareTester および User の各ロールがアクセスできるようになります。

エンドポイントセキュリティーアノテーションと Jakarta REST 継承

Quarkus は、次の例のように、エンドポイント実装またはそのクラスに配置されたセキュリティーアノテーションをサポートしています。

@Path("hello")
public interface HelloInterface {

    @GET
    String hello();

}

@DenyAll (1)
public class HelloInterfaceImpl implements HelloInterface {

    @RolesAllowed("admin") (2)
    @Override
    public String hello() {
        return "Hello";
    }
}
1 クラスレベルのセキュリティーアノテーションは、エンドポイント実装が宣言されているクラスに配置する必要があります。
2 メソッドレベルのセキュリティーアノテーションはエンドポイント実装に配置する必要があります。

デフォルトのインターフェイスメソッドとして宣言された RESTEasy サブリソースロケーターは 標準のセキュリティーアノテーションでは保護できません 保護されたサブリソースロケーターは、次の例のように、インターフェイス実装者に実装され、保護される必要があります。

@Path("hello")
public interface HelloInterface {

    @RolesAllowed("admin")
    @Path("sub")
    default HelloSubResource wrongWay() {
        // not supported
    }

    @Path("sub")
    HelloSubResource rightWay();

}

public class HelloInterfaceImpl implements HelloInterface {

    @RolesAllowed("admin")
    @Override
    public HelloSubResource rightWay() {
        return new HelloSubResource();
    }
}

権限アノテーション

Quarkus は、指定された権限を持つ認証済みユーザーにリソースへのアクセスを認可する io.quarkus.security.PermissionsAllowed アノテーションも提供します。 このアノテーションは、一般的なセキュリティーアノテーションのエクステンションであり、SecurityIdentity インスタンスに付与された権限をチェックします。

@PermissionsAllowed アノテーションで保護されたエンドポイントの例
package org.acme.crud;

import io.quarkus.arc.Arc;
import io.vertx.ext.web.RoutingContext;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

import io.quarkus.security.PermissionsAllowed;

import java.security.BasicPermission;
import java.security.Permission;
import java.util.Collection;
import java.util.Collections;

@Path("/crud")
public class CRUDResource {

    @PermissionsAllowed("create") (1)
    @PermissionsAllowed("update")
    @POST
    @Path("/modify/repeated")
    public String createOrUpdate() {
        return "modified";
    }

    @PermissionsAllowed(value = {"create", "update"}, inclusive=true) (2)
    @POST
    @Path("/modify/inclusive")
    public String createOrUpdate(Long id) {
        return id + " modified";
    }

    @PermissionsAllowed({"see:detail", "see:all", "read"}) (3)
    @GET
    @Path("/id/{id}")
    public String getItem(String id) {
        return "item-detail-" + id;
    }

    @PermissionsAllowed(value = "list", permission = CustomPermission.class) (4)
    @Path("/list")
    @GET
    public Collection<String> list(@QueryParam("query-options") String queryOptions) {
        // your business logic comes here
        return Collections.emptySet();
    }

    public static class CustomPermission extends BasicPermission {

        public CustomPermission(String name) {
            super(name);
        }

        @Override
        public boolean implies(Permission permission) {
            var event = Arc.container().instance(RoutingContext.class).get(); (5)
            var publicContent = "public-content".equals(event.request().params().get("query-options"));
            var hasPermission = getName().equals(permission.getName());
            return hasPermission && publicContent;
        }
    }
}
1 リソースメソッド createOrUpdate にアクセスできるのは、 create 権限と update 権限の両方を持つユーザーのみです。
2 デフォルトでは、1 つのアノテーションインスタンスを通じて指定された権限の少なくとも 1 つが必要です。 inclusive=true を設定することで、すべての権限を要求することができます。 両方のリソースメソッド createOrUpdate には同等の認可要件があります。
3 SecurityIdentityread 権限または see 権限と、all アクションまたは detail アクションのどちらかがある場合は、getItem へのアクセスが許可されます。
4 希望する java.security.Permission 実装を使用できます。 デフォルトでは、文字列ベースの権限は io.quarkus.security.StringPermission によって実行されます。
5 権限は Bean ではないため、Bean インスタンスを取得する唯一の方法は、Arc.container() を使用してプログラム的に取得することです。
IO スレッドで @PermissionsAllowed を使用する予定の場合は Proactive Authentication の情報を確認してください。
Quarkus インターセプターの制限により、@PermissionsAllowed はクラスレベルで繰り返すことができません。 詳細は、Quarkus CDI リファレンスガイドの Repeatable interceptor bindings セクションを参照してください。

ロール対応の SecurityIdentity インスタンスに権限を追加する最も簡単な方法は、ロールを権限にマップすることです。 設定を利用した認可 を使用して、次の例に示すように、認証されたリクエストに対して CRUDResource エンドポイントに必要な SecurityIdentity 権限を付与します。

quarkus.http.auth.policy.role-policy1.permissions.user=see:all                                      (1)
quarkus.http.auth.policy.role-policy1.permissions.admin=create,update,read                          (2)
quarkus.http.auth.permission.roles1.paths=/crud/modify/*,/crud/id/*                                 (3)
quarkus.http.auth.permission.roles1.policy=role-policy1

quarkus.http.auth.policy.role-policy2.permissions.user=list
quarkus.http.auth.policy.role-policy2.permission-class=org.acme.crud.CRUDResource$CustomPermission  (4)
quarkus.http.auth.permission.roles2.paths=/crud/list
quarkus.http.auth.permission.roles2.policy=role-policy2
1 user ロールの SecurityIdentity インスタンスに、権限 see とアクション all を追加します。 同様に、@PermissionsAllowed アノテーションの場合は、デフォルトで io.quarkus.security.StringPermission が使用されます。
2 権限 createupdate、および read は、ロール admin にマップされます。
3 ロールポリシー role-policy1 は、認証されたリクエストのみが /crud/modify および /crud/id サブパスにアクセスできるようにします。 パスマッチングアルゴリズムの詳細は、このガイドの 複数のパスのマッチング:一番長いパスが勝ちます。 を参照してください。
4 java.security.Permission クラスのカスタム実装を指定できます。 カスタムクラスでは、権限名と、オプションでいくつかのアクション (たとえば、String 配列) を受け入れるコンストラクターを 1 つだけ定義する必要があります。 このシナリオでは、権限 listnew CustomPermission ("list") として SecurityIdentity インスタンスに追加されます。

You can also create a custom java.security.Permission class with additional constructor parameters. These additional parameters names get matched with arguments names of the method annotated with the @PermissionsAllowed annotation. Later, Quarkus instantiates your custom permission with actual arguments, with which the method annotated with the @PermissionsAllowed has been invoked.

追加の引数を受け入れるカスタムの java.security.Permission クラスの例
package org.acme.library;

import java.security.Permission;
import java.util.Arrays;
import java.util.Set;

public class LibraryPermission extends Permission {

    private final Set<String> actions;
    private final Library library;

    public LibraryPermission(String libraryName, String[] actions, Library library) { (1)
        super(libraryName);
        this.actions = Set.copyOf(Arrays.asList(actions));
        this.library = library;
    }

    @Override
    public boolean implies(Permission requiredPermission) {
        if (requiredPermission instanceof LibraryPermission) {
            LibraryPermission that = (LibraryPermission) requiredPermission;
            boolean librariesMatch = getName().equals(that.getName());
            boolean requiredLibraryIsSublibrary = library.isParentLibraryOf(that.library);
            boolean hasOneOfRequiredActions = that.actions.stream().anyMatch(actions::contains);
            return (librariesMatch || requiredLibraryIsSublibrary) && hasOneOfRequiredActions;
        }
        return false;
    }

    // here comes your own implementation of the `java.security.Permission` class methods

    public static abstract class Library {

        protected String description;

        abstract boolean isParentLibraryOf(Library library);

    }

    public static class MediaLibrary extends Library {

        @Override
        boolean isParentLibraryOf(Library library) {
            return library instanceof MediaLibrary;
        }
    }

    public static class TvLibrary extends MediaLibrary {
        // TvLibrary specific implementation of the 'isParentLibraryOf' method
    }
}
1 カスタム Permission クラスのコンストラクターは 1 つだけ存在する必要があります。 最初のパラメーターは常に権限名とみなされ、String 型である必要があります。 Quarkus はオプションでコンストラクターに権限アクションを渡すことができます。 これを実現するには、2 番目のパラメーターを String[] として宣言します。

LibraryPermission クラスは、SecurityIdentityreadwrite、または list などの必要なアクションのいずれかを実行することを許可されている場合に、現在のライブラリーまたは親ライブラリーへのアクセスを許可します。

次の例は、LibraryPermission クラスの使用方法を示しています。

package org.acme.library;

import io.quarkus.security.PermissionsAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import org.acme.library.LibraryPermission.Library;

@ApplicationScoped
public class LibraryService {

    @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class) (1)
    public Library updateLibrary(String newDesc, Library library) {
        library.description = newDesc;
        return library;
    }

    @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class) (2)
    @PermissionsAllowed(value = {"tv:read", "tv:list"}, permission = LibraryPermission.class)
    public Library migrateLibrary(Library migrate, Library library) {
        // migrate libraries
        return library;
    }

}
1 The formal parameter library is identified as the parameter matching same-named LibraryPermission constructor parameter. Therefore, Quarkus will pass the library parameter to the LibraryPermission class constructor. However, the LibraryPermission must be instantiated each time the updateLibrary method is invoked.
2 Here, the second Library parameter matches the name library, while the migrate parameter is ignored during the LibraryPermission permission instantiation.
LibraryPermission で保護されたリソースの例
package org.acme.library;

import io.quarkus.security.PermissionsAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.acme.library.LibraryPermission.Library;

@Path("/library")
public class LibraryResource {

    @Inject
    LibraryService libraryService;

    @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class)
    @PUT
    @Path("/id/{id}")
    public Library updateLibrary(@PathParam("id") Integer id, Library library) {
        ...
    }

    @PUT
    @Path("/service-way/id/{id}")
    public Library updateLibrarySvc(@PathParam("id") Integer id, Library library) {
        String newDescription = "new description " + id;
        return libraryService.updateLibrary(newDescription, library);
    }

}

CRUDResource の例と同様に、次の例は、admin ロールを持つユーザーに MediaLibrary を更新する権限を付与する方法を示しています。

package org.acme.library;

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection (1)
public class MediaLibraryPermission extends LibraryPermission {

    public MediaLibraryPermission(String libraryName, String[] actions) {
        super(libraryName, actions, new MediaLibrary());    (2)
    }

}
1 ネイティブ実行可能ファイルを構築する場合、少なくとも 1 つの io.quarkus.security.PermissionsAllowed#name パラメーターでも使用されていない限り、権限クラスをリフレクション用に登録する必要があります。@RegisterForReflection アノテーションの詳細は、native application tips ページを参照してください。
2 MediaLibrary インスタンスを LibraryPermission コンストラクターに渡します。
quarkus.http.auth.policy.role-policy3.permissions.admin=media-library:list,media-library:read,media-library:write   (1)
quarkus.http.auth.policy.role-policy3.permission-class=org.acme.library.MediaLibraryPermission
quarkus.http.auth.permission.roles3.paths=/library/*
quarkus.http.auth.permission.roles3.policy=role-policy3
1 media-library 権限を付与します。これにより、readwrite、および list アクションが許可されます。 MediaLibraryTvLibrary クラスの親であるため、admin ロールを持つユーザーも TvLibrary を変更することが許可されます。
/library/*`パスは Keycloak プロバイダーの Dev UI ページからテストできます。これは、Dev Services for Keycloak によって自動的に作成されたユーザー`aliceadmin ロールが付与されるためです。

これまでに示した例は、ロールと権限のマッピングを示しています。 SecurityIdentity インスタンスにプログラムで権限を追加することも可能です。 次の例では、SecurityIdentity がカスタマイズされ、以前に HTTP ロールベースポリシーで付与されたものと同じ権限が追加されます。

プログラムで SecurityIdentityLibraryPermission を追加する例
import java.security.Permission;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class PermissionsIdentityAugmentor implements SecurityIdentityAugmentor {

    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
        if (isNotAdmin(identity)) {
            return Uni.createFrom().item(identity);
        }
        return Uni.createFrom().item(build(identity));
    }

    private boolean isNotAdmin(SecurityIdentity identity) {
        return identity.isAnonymous() || !"admin".equals(identity.getPrincipal().getName());
    }

    SecurityIdentity build(SecurityIdentity identity) {
        return QuarkusSecurityIdentity.builder(identity)
                .addPermission(new MediaLibraryPermission("media-library", new String[] { "read", "write", "list"}); (1)
                .build();
    }

}
1 Add a media-library permission that was created can perform read, write, and list actions. Because MediaLibrary is the TvLibrary class parent, a user with the admin role is also permitted to modify TvLibrary.
jakarta.ws.rs.core.SecurityContext には権限がないため、アノテーションベースの権限はカスタム Jakarta REST SecurityContexts では機能しません。

Create permission checkers

By default, SecurityIdentity must be configured with permissions which can be used to check if this identity passes @PermissionAllowed authorization restrictions. Alternatively, you can use a @PermissionChecker annotation to mark any CDI bean method as a permission checker. The @PermissionChecker annotation value should match required permission declared by the @PermissionsAllowed annotation value. For example, a permission checker can be created like this:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestPath;

@Path("/project/{projectName}")
public class ProjectResource {

    @PermissionsAllowed("rename-project") (1)
    @POST
    public void renameProject(@RestPath String projectName, @RestForm String newName) {
        Project project = Project.findByName(projectName);
        project.name = newName;
    }

    @PermissionChecker("rename-project") (2)
    boolean canRenameProject(SecurityIdentity identity, String projectName) { (3) (4)
        var principalName = identity.getPrincipal().getName();
        var user = User.getUserByName(principalName);
        return userOwnsProject(projectName, user);
    }
}
1 The permission required to access the ProjectResource#renameProject is the rename-project permission.
2 The ProjectResource#canRenameProject method authorizes access to the ProjectResource#renameProject endpoint.
3 The SecurityIdentity instance can be injected into any permission checker method.
4 In this example, the rename-project permission checker is declared on the same resource. However, there is no restriction on where this permission checker can be declared as demonstrated in the next example.
Permission checker methods can be declared on a normal scoped CDI bean or on a @Singleton bean. The @Dependent CDI bean scope is currently not supported.

The permission checker above requires the SecurityIdentity instance to authorize the renameProject endpoint. Instead of declaring the rename-project permission checker directly on the resource, you can declare it on any CDI bean like in this example:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionChecker;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped (1)
public class ProjectPermissionChecker {

    @PermissionChecker("rename-project")
    boolean canRenameProject(String projectName, SecurityIdentity identity) {   (2)
        var principalName = identity.getPrincipal().getName();
        var user = User.getUserByName(principalName);
        return userOwnsProject(projectName, user);
    }

}
1 A CDI bean with the permission checker must be either a normal scoped bean or a @Singleton bean.
2 The permission checker method must return either boolean or Uni<Boolean>. Private checker methods are not supported.
Permission checks run by default on event loops. Annotate a permission checker method with the io.smallrye.common.annotation.Blocking annotation if you want to run the check on a worker thread.

Create permission meta-annotations

@PermissionsAllowed can also be used in meta-annotations. For example, a new @CanWrite security annotation can be created like this:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import io.quarkus.security.PermissionsAllowed;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@PermissionsAllowed(value = "write", permission = CustomPermission.class) (1)
public @interface CanWrite {

}
1 Any method or class annotated with the @CanWrite annotation is secured with this @PermissionsAllowed annotation instance.

Pass @BeanParam parameters into a custom permission

Quarkus can map fields of a secured method parameters to a custom permission constructor parameters. You can use this feature to pass jakarta.ws.rs.BeanParam parameters into your custom permission. Let’s consider following Jakarta REST resource:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionsAllowed;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/hello")
public class HelloResource {

    @PermissionsAllowed(value = "say-hello", params = "beanParam.securityContext.userPrincipal.name") (1)
    @GET
    public String sayHello(@BeanParam SimpleBeanParam beanParam) {
        return "Hello from " + beanParam.uriInfo.getPath();
    }

}
1 The params annotation attribute specifies that user principal name should be passed to the BeanParamPermissionChecker#canSayHello method. Other BeanParamPermissionChecker#canSayHello method parameters like customAuthorizationHeader and query are matched automatically. Quarkus identifies the BeanParamPermissionChecker#canSayHello method parameters among beanParam fields and their public accessors. To avoid ambiguous resolution, automatic detection only works for the beanParam fields. For that reason, we had to specify path to the user principal name explicitly.

Where the SimpleBeanParam class is declared like in the example below:

package org.acme.security.rest.dto;

import java.util.List;

import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;

public class SimpleBeanParam {

    @HeaderParam("CustomAuthorization")
    private String customAuthorizationHeader;

    @Context
    SecurityContext securityContext;

    @Context
    public UriInfo uriInfo;

    @QueryParam("query")
    public String query;    (1)

    public SecurityContext getSecurityContext() { (2)
        return securityContext;
    }

    public String customAuthorizationHeader() { (3)
        return customAuthorizationHeader;
    }
}
1 Quarkus Security can only pass public fields to a custom permission constructor.
2 Quarkus Security automatically uses public getter methods if they are available.
3 The customAuthorizationHeader field is not public, therefore Quarkus access this field with the customAuthorizationHeader accessor. That is particularly useful with Java records, where generated accessors are not prefixed with get.

Here is an example of a @PermissionChecker method that checks the say-hello permission based on a user principal, custom header and query parameter:

package org.acme.security.permission;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.security.PermissionChecker;

@ApplicationScoped
public class BeanParamPermissionChecker {

    @PermissionChecker("say-hello")
    boolean canSayHello(String customAuthorizationHeader, String name, String query) {
        boolean queryParamAllowedForPermissionName = checkQueryParams(query);
        boolean usernameWhitelisted = isUserNameWhitelisted(name);
        boolean customAuthorizationMatches = checkCustomAuthorization(customAuthorizationHeader);
        return queryParamAllowedForPermissionName && usernameWhitelisted && customAuthorizationMatches;
    }

    ...
}
You can pass @BeanParam directly into a @PermissionChecker method and access its fields programmatically. Ability to reference @BeanParam fields with the @PermissionsAllowed#params attribute is useful when you have multiple differently structured @BeanParam classes.

関連コンテンツ