ウェブエンドポイントの認可
Quarkus には、プラグ可能な Web セキュリティーレイヤーが組み込まれています。 セキュリティーがアクティブな場合、システムはすべての HTTP リクエストに対して権限チェックを実行し、続行するかどうかを決定します。
quarkus.http.auth.
設定によってパスが制限されている場合、 @PermitAll
を使用してもパスは開きません。
特定のパスにアクセスできるようにするには、Quarkus のセキュリティー設定内で適切な設定を行う必要があります。
Jakarta RESTful Web サービスを使用する場合は、HTTP パスレベルのマッチングではなく、 |
認可は、セキュリティ・プロバイダーが提供するユーザー・ロールに基づきます。これらのロールをカスタマイズするには、
SecurityIdentityAugmentor
を作成することができます。
セキュリティ・アイデンティティのカスタマイズ を参照してください。
設定を利用した認可
権限は、Quarkus 設定で権限セットによって定義されます。各権限セットで、アクセス制御用のポリシーを指定します。
組込ポリシー | 説明 |
---|---|
|
このポリシーは、すべてのユーザーを拒否します。 |
|
このポリシーは、すべてのユーザーを許可します。 |
|
このポリシーは、認証されたユーザーのみを許可します。 |
特定のロールを持つユーザーにリソースへのアクセスを許可するロールベースのポリシーを定義できます。
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 CheckResult.permit();
}
return 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 メソッドによって返される値とマッチする必要があります。 |
または、 @AuthorizationPolicy
セキュリティーアノテーションを使用して、カスタム名 HttpSecurityPolicy を Jakarta REST エンドポイントにバインドすることもできます。
1 | @AuthorizationPolicy アノテーションは、他の標準セキュリティーアノテーションと合わせて使用できます。
通常どおり、メソッドレベルのアノテーションはクラスレベルのアノテーションよりも優先されます。 |
2 | カスタム名の HttpSecurityPolicy を Jakarta REST hello エンドポイントに適用します。 |
すべてのリクエストで呼び出されるグローバル |
@RequestScoped
Bean の HttpSecurityPolicy
への挿入
@RequestScoped
Bean は、CDI リクエストコンテキスト がアクティブな場合にのみ挿入できます。
コンテキストは、 @ActivateRequestContext
などを使用してユーザーが有効にできますが、Quarkus が複数の @RequestScoped
Bean を準備する前に認可が行われます。
Quarkus を有効にして CDI リクエストコンテキストを準備することを推奨します。
たとえば、 jakarta.ws.rs.core.UriInfo
Bean などの Jakarta REST コンテキストから Bean を挿入する状況を考えてみましょう。
この場合、Jakarta REST エンドポイントに HttpSecurityPolicy
を適用する必要があります。これは、次のいずれかの方法で実現できます。
* @AuthorizationPolicy
セキュリティーアノテーションを使用します。
* quarkus.http.auth.permission.custom1.applies-to=jaxrs
設定プロパティーを設定します。
パスとメソッドのマッチング
権限セットでは、パスとメソッドをコンマ区切りのリストとして指定することもできます。
パスが *
ワイルドカードで終わる場合、生成されるクエリーはすべてのサブパスにマッチします。
それ以外の場合は、完全一致を照会し、特定のパスにのみマッチします。
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
上記の権限セットの場合、 |
前述の このルールをさらに例示するために、サブパス権限ではパブリックリソースへのアクセスを許可し、ルートパス権限では認可を要求するシナリオを示します。
|
複数のサブパスのマッチング: *
ワイルドカードへの最長パスが優先される
前の例では、パスの末尾が *
ワイルドカードの場合に、すべてのサブパスがマッチすることを
示しました。
このワイルドカードはパスの途中にも適用され、単一のパスセグメントを表します。
他のパスセグメント文字と混在させることはできません。したがって、パス区切り文字は常に *
ワイルドカードを囲みます (例: /public/*/about-us
)。
複数のパスパターンが同じリクエストパスに対応する場合、システムは *
ワイルドカードにつながる最長のサブパスを選択します。
この文脈では、すべてのパスセグメント文字は、 *
ワイルドカード
よりも具体的になります。
簡単な例を挙げてみます。
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) を可能にするセキュリティーが組み込まれています。
アノテーション型 | 説明 |
---|---|
|
どのセキュリティロールも指定されたメソッドを呼び出すことを許可されていないことを指定します。 |
|
すべてのセキュリティロールが指定されたメソッドを呼び出すことを許可されるように指定します。
|
|
アプリケーション内のメソッドへのアクセスを許可するセキュリティーロールのリストを指定します。 |
|
Quarkus は、認証されたすべてのユーザーがリソースにアクセスできるようにする |
|
指定されたメソッドを呼び出すことが許可される権限のリストを指定します。 |
|
指定された Jakarta REST エンドポイントへのアクセスを許可する名前付き |
次の 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 | /subject/authenticated エンドポイントでは、 @Authenticated アノテーションを指定して、認証されたすべてのユーザーを許可します。 |
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 インスタンスに追加されます。 |
追加のコンストラクターパラメーターを使用してカスタム java.security.Permission
クラスを作成することもできます。
これらの追加パラメーターの名前は、 @PermissionsAllowed
アノテーションが付けられたメソッドの引数名と一致します。
その後、Quarkus は、 @PermissionsAllowed
アノテーションが付けられたメソッドが呼び出された実際の引数を使用して、カスタム権限をインスタンス化します。
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 | 仮パラメーター library は、同じ名前の LibraryPermission コンストラクターパラメーターと一致するパラメーターとして識別されます。
したがって、Quarkus は library パラメーターを LibraryPermission クラスコンストラクターに渡します。
ただし、 updateLibrary メソッドが呼び出されるたびに LibraryPermission をインスタンス化する必要があります。 |
2 | ここで、2 番目の Library パラメーターは名前 library と一致しますが、
migrate パラメーターは LibraryPermission 権限のインスタンス化中に無視されます。 |
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 | 作成された権限 media-library を追加すると、 read 、 write 、および list アクションを実行できます。
MediaLibrary は TvLibrary クラスの親であるため、 admin ロールを持つユーザーも TvLibrary を変更することが許可されます。 |
jakarta.ws.rs.core.SecurityContext には権限がないため、アノテーションベースの権限はカスタム Jakarta REST SecurityContexts では機能しません。
|
権限チェッカーを作成する
デフォルトでは、 Security アイデンティティー
は、この ID が @PermissionAllowed
認可制限を通過するかどうかを確認するために使用できる権限で設定されている必要があります。
または、 @PermissionChecker
アノテーションを使用して、任意の CDI Bean メソッドを権限チェッカーとしてマークすることもできます。
@PermissionChecker
アノテーション値は、 @PermissionsAllowed
アノテーション値によって宣言された必要な権限と一致する必要があります。
たとえば、権限チェッカーは次のように作成できます。
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 | ProjectResource#renameProject にアクセスするために必要な権限は、 rename-project 権限です。 |
2 | ProjectResource#canRenameProject メソッドは、 ProjectResource#renameProject エンドポイントへのアクセスを承認します。 |
3 | SecurityIdentity インスタンスは、任意の権限チェッカーメソッドに挿入できます。 |
4 | この例では、 rename-project 権限チェッカーが同じリソースで宣言されています。
ただし、次の例に示すように、この権限チェッカーを宣言できる場所に制限はありません。 |
権限チェッカーメソッドは、通常のスコープの CDI Bean または @Singleton Bean で宣言できます。
@Dependent CDI Bean スコープは現在サポートされていません。
|
上記の権限チェッカーでは、 renameProject
エンドポイントを承認するために SecurityIdentity
インスタンスが必要です。
rename-project
権限チェッカーをリソース上で直接宣言する代わりに、次の例のように任意の CDI Bean 上で宣言できます。
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 | 権限チェッカーを備えた CDI Bean は、通常のスコープ Bean または @Singleton Bean のいずれかである必要があります。 |
2 | 権限チェッカーメソッドは、 boolean または Uni<Boolean> を返す必要があります。プライベートチェッカーメソッドはサポートされていません。 |
権限チェックは、イベントループでデフォルトで実行されます。
ワーカースレッドでチェックを実行する場合は、権限チェッカーメソッドに io.smallrye.common.annotation.Blocking アノテーションを付けます。
|
@PermissionsAllowed
値と @PermissionChecker
値の一致は、次の例に示すように、文字列の等価性に基づいて行われます。
package org.acme.security;
import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class FileService {
@PermissionsAllowed({ "delete:all", "delete:dir" }) (1)
void deleteDirectory(Path directoryPath) {
// delete directory
}
@PermissionsAllowed(value = { "delete:service", "delete:file" }, inclusive = true) (2)
void deleteServiceFile(Path serviceFilePath) {
// delete service file
}
@PermissionChecker("delete:all")
boolean canDeleteAllDirectories(SecurityIdentity identity) {
String filePermissions = identity.getAttribute("user-group-file-permissions");
return filePermissions != null && filePermissions.contains("w");
}
@PermissionChecker("delete:service")
boolean canDeleteService(SecurityIdentity identity) {
return identity.hasRole("admin");
}
@PermissionChecker("delete:file")
boolean canDeleteFile(Path serviceFilePath) {
return serviceFilePath != null && !serviceFilePath.endsWith("critical");
}
}
1 | 権限チェッカーメソッド canDeleteAllDirectories は、 delete:all 値が等しいため、 deleteDirectory へのアクセスを許可します。 |
2 | 権限チェッカーメソッドは 2 つ必要です。1 つは delete:service 権限用、もう 1 つは delete:file 権限用です。 |
権限メタアノテーションを作成する
@PermissionsAllowed
はメタアノテーションでも使用できます。
たとえば、新しい @CanWrite
セキュリティーアノテーションは次のように作成できます。
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 | @CanWrite アノテーションが付けられたメソッドまたはクラスは、この @PermissionsAllowed アノテーションインスタンスにより保護されます。 |
@BeanParam
パラメーターをカスタム権限に渡す
Quarkus は、保護されたメソッドパラメーターのフィールドをカスタム権限コンストラクターパラメーターにマップできます。
この機能を使用すると、 jakarta.ws.rs.BeanParam
パラメーターをカスタム権限に渡すことができます。
次の Jakarta REST リソースを考えてみましょう。
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 | params アノテーション属性は、ユーザープリンシパル名を BeanParamPermissionChecker#canSayHello メソッドに渡すことを指定します。
customAuthorizationHeader や query などの他の BeanParamPermissionChecker#canSayHello メソッドパラメーターは自動的に一致します。
Quarkus は、 beanParam フィールドとそのパブリックアクセサーの中から BeanParamPermissionChecker#canSayHello メソッドパラメーターを識別します。
あいまいな解決を避けるため、自動検出は beanParam フィールドに対してのみ機能します。
そのため、ユーザープリンシパル名へのパスを明示的に指定する必要がありました。 |
SimpleBeanParam
クラスは以下の例のように宣言されています。
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 は、カスタム権限コンストラクターにパブリックフィールドのみを渡すことができます。 |
2 | Quarkus Security は、パブリックゲッターメソッドが利用可能な場合はそれを自動的に使用します。 |
3 | customAuthorizationHeader フィールドはパブリックではないため、Quarkus は customAuthorizationHeader アクセサーを使用してこのフィールドにアクセスします。
これは、生成されたアクセサーに接頭辞 get が付かない Java レコードで特に便利です。 |
以下は、ユーザープリンシパル、カスタムヘッダー、クエリーパラメーターに基づいて say-hello
権限をチェックする @PermissionChecker
メソッドの例です。
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;
}
...
}
@BeanParam を @PermissionChecker メソッドに直接渡し、プログラムでそのフィールドにアクセスできます。
@PermissionsAllowed#params 属性を使用して @BeanParam フィールドを参照する機能は、異なる構造の @BeanParam クラスが複数ある場合に便利です。
|