ウェブエンドポイントの認可
Quarkus には、プラグ可能な Web セキュリティーレイヤーが組み込まれています。 セキュリティーがアクティブな場合、システムはすべての HTTP リクエストに対して権限チェックを実行し、続行するかどうかを決定します。
quarkus.http.auth.
設定によってパスが制限されている場合、@PermitAll
を使用してもパスは開きません。
特定のパスにアクセスできるようにするには、Quarkus のセキュリティー設定内で適切な設定を行う必要があります。
Jakarta RESTful Web サービスを使用する場合は、HTTP パスレベルのマッチングではなく、 |
認可は、セキュリティ・プロバイダーが提供するユーザー・ロールに基づきます。これらのロールをカスタマイズするには、
SecurityIdentityAugmentor
を作成することができます。
セキュリティ・アイデンティティのカスタマイズ を参照してください。
設定を利用した認可
権限は、Quarkus 設定で権限セットによって定義されます。各権限セットで、アクセス制御用のポリシーを指定します。
組込ポリシー | Description |
---|---|
|
このポリシーは、すべてのユーザーを拒否します。 |
|
このポリシーは、すべてのユーザーを許可します。 |
|
このポリシーは、認証されたユーザーのみを許可します。 |
特定のロールを持つユーザーにリソースへのアクセスを許可するロールベースのポリシーを定義できます。
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 は例の名前です。権限セットには任意の名前を付けることができます。 |
上記の例の正確なパスパターン
|
カスタムの 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 パスマッチングルールにマッチするリクエストにのみ適用されます。 |
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.
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. |
すべてのリクエストで呼び出されるグローバル |
パスとメソッドのマッチング
権限セットでは、パスとメソッドをコンマ区切りのリストとして指定することもできます。
パスが *
ワイルドカードで終わる場合、生成されるクエリーはすべてのサブパスにマッチします。
それ以外の場合は、完全一致を照会し、特定のパスにのみマッチします。
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 つ以上の権限セットにマッチしても、必要なメソッドのいずれにもマッチしない場合、リクエストは拒否されます。
上記の権限セットが与えられている場合、 |
複数のパスのマッチング:一番長いパスが勝ちます。
マッチングは常に「最長パスが優先される」という基準で行われます。 具体的な権限セットがマッチした場合、それよりも具体性に欠ける権限セットは考慮されません。
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
上記の権限セットの場合、 |
前述の このルールをさらに例示するために、サブパス権限ではパブリックリソースへのアクセスを許可し、ルートパス権限では認可を要求するシナリオを示します。
|
複数のサブパスのマッチング: *
ワイルドカードへの最長パスが優先される
前の例では、パスの末尾が *
ワイルドカードの場合に、すべてのサブパスがマッチすることを
示しました。
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
/*
パスの末尾にある |
複数のパスのマッチング:一番具体的なパスが勝ちます
パスが複数の権限セットに登録されている場合、リクエストにマッチする 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
上記の権限セットは、 一方、 |
複数のパスのマッチング:一番長いパスが勝ちます
場合によっては、前述のルールにより、複数の権限セットが同時に適用されることがあります。 その場合、リクエストを続行するには、すべての権限でアクセスが許可されている必要があります。 そのためには、両方にメソッドが指定されているか、メソッドが存在しない必要があります。 メソッド固有のマッチが優先されます。
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 は両方の権限セットのパスにマッチするため、user と admin の両方のロールが必要です。
|
アクセスを拒否するための設定プロパティ
以下の設定により、ロールベースアクセスコントロール(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 つだけ適用されることです。 当然、同じ優先パスを持つ権限を必要なだけ指定することができ、それらはすべて適用されます。 ただし、何度も繰り返さずに多くのパスに適用する権限チェックが必要になる場合があります。 そこで共有権限チェックを使用します。共有権限チェックは、権限パスがマッチしたときに常に適用されます。
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) を可能にするセキュリティーが組み込まれています。
アノテーション型 | Description |
---|---|
|
どのセキュリティロールも指定されたメソッドを呼び出すことを許可されていないことを指定します。 |
|
すべてのセキュリティロールが指定されたメソッドを呼び出すことを許可されるように指定します。
|
|
Specifies the list of security roles allowed to access methods in an application. |
|
Quarkus provides the |
|
Specifies the list of permissions that are allowed to invoke the specified methods. |
|
Specifies named |
次の SubjectExposingResourceの例 は、エンドポイントを記述および保護するために Jakarta REST と Common Security アノテーションの両方を使用するエンドポイントを示します。
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 をサポートします。
アノテーションで使用される設定プロパティーはランタイム時に解決されます。
アノテーション | 値の説明 |
---|---|
|
エンドポイントは、 |
|
値が複数の変数を含むことができることを示す例。 |
|
デフォルト値のデモンストレーション。
必要なロールは、 |
@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 として扱われます。そのため、このエンドポイントには、Administrator 、Software 、Tester および 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 サブリソースロケーターは 標準のセキュリティーアノテーションでは保護できません 保護されたサブリソースロケーターは、次の例のように、インターフェイス実装者に実装され、保護される必要があります。
|
権限アノテーション
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 | SecurityIdentity に read 権限または 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 | 権限 create 、update 、および read は、ロール admin にマップされます。 |
3 | ロールポリシー role-policy1 は、認証されたリクエストのみが /crud/modify および /crud/id サブパスにアクセスできるようにします。
パスマッチングアルゴリズムの詳細は、このガイドの 複数のパスのマッチング:一番長いパスが勝ちます。 を参照してください。 |
4 | java.security.Permission クラスのカスタム実装を指定できます。
カスタムクラスでは、権限名と、オプションでいくつかのアクション (たとえば、String 配列) を受け入れるコンストラクターを 1 つだけ定義する必要があります。
このシナリオでは、権限 list が new 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
クラスは、SecurityIdentity
が read
、write
、または 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 権限を付与します。これにより、read 、write 、および list アクションが許可されます。
MediaLibrary は TvLibrary クラスの親であるため、admin ロールを持つユーザーも TvLibrary を変更することが許可されます。 |
/library/*`パスは Keycloak プロバイダーの Dev UI ページからテストできます。これは、Dev Services for Keycloak によって自動的に作成されたユーザー`alice に
admin ロールが付与されるためです。
|
これまでに示した例は、ロールと権限のマッピングを示しています。
SecurityIdentity
インスタンスにプログラムで権限を追加することも可能です。
次の例では、SecurityIdentity
がカスタマイズされ、以前に HTTP ロールベースポリシーで付与されたものと同じ権限が追加されます。
SecurityIdentity
に LibraryPermission
を追加する例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.
|