Web エンドポイントの認可
概要
Red Hat build of Quarkus ドキュメントへのフィードバックの提供
エラーを報告したり、ドキュメントを改善したりするには、Red Hat Jira アカウントにログインし、課題を送信してください。Red Hat Jira アカウントをお持ちでない場合は、アカウントを作成するように求められます。
手順
- 次のリンクをクリックして チケットを作成します。
- Summary に課題の簡単な説明を入力します。
- Description に課題や機能拡張の詳細な説明を入力します。問題があるドキュメントのセクションへの URL も記載してください。
- Submit をクリックすると、課題が作成され、適切なドキュメントチームに転送されます。
第1章 Web エンドポイントの認可
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
を作成します。Security Identity Customization を参照してください。
1.1. 設定を使用した認可
権限は、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
上記の例の正確なパスパターン /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/
パスは保護されていません。
1.1.1. カスタムの 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
パスマッチングルールにマッチするリクエストにのみ適用されます。
設定ファイルから参照されるカスタム名の 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
メソッドにより返される値とマッチする必要があります。
または、@AuthorizationPolicy
セキュリティーアノテーションを使用して、カスタム名 HttpSecurityPolicy を Jakarta REST エンドポイントにバインドすることもできます。
すべてのリクエストで呼び出されるグローバルの HttpSecurityPolicy
を作成することもできます。io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name
メソッドを実装せず、ポリシーに名前を付けないままにしてください。
1.1.2. HttpSecurityPolicy
への @RequestScoped
Bean の注入
@RequestScoped
Bean は、CDI リクエストコンテキスト がアクティブな場合にのみ注入できます。コンテキストは、@ActivateRequestContext
などを使用してユーザーがアクティブ化できますが、Quarkus がいくつかの @RequestScoped
Bean を準備する前に、認可が行われます。CDI リクエストコンテキストは、Quarkus にアクティブ化および準備させることを推奨します。たとえば、jakarta.ws.rs.core.UriInfo
Bean などの Jakarta REST コンテキストから Bean を注入する状況を考えてみましょう。この場合、HttpSecurityPolicy
を Jakarta REST エンドポイントに適用する必要があります。これは、次のいずれかの方法で実現できます。* @AuthorizationPolicy
セキュリティーアノテーションを使用する。* quarkus.http.auth.permission.custom1.applies-to=jaxrs
設定プロパティーを設定する。
1.1.3. パスとメソッドのマッチング
権限セットでは、パスとメソッドをコンマ区切りリスト形式で指定することもできます。パスの末尾がワイルドカード *
の場合は、そのパスによって生成されたクエリーがすべてのサブパスにマッチします。それ以外の場合は、クエリーが完全一致を照会し、特定のパスにのみマッチします。
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.1.4. パスにはマッチするがメソッドにはマッチしない場合
パスに基づいて 1 つ以上の権限セットにマッチしても、必要なメソッドのいずれにもマッチしないと、リクエストは拒否されます。
上記の権限セットが指定されていると、GET/public/foo
は、パスとメソッドの両方にマッチするため、許可されます。対照的に、POST/public/foo
は、パスにはマッチしますが、メソッドにはマッチしないため、拒否されます。
1.1.5. 複数のパスのマッチング: 最長パスが優先される
マッチングは、常に "最長パスが優先される" という基準で行われます。より詳細な権限セットがマッチした場合、それよりも詳細でない権限セットは考慮されません。
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
1.1.6. 複数のサブパスのマッチング: *
ワイルドカードへの最長パスが優先される
前の例では、パスの末尾が *
ワイルドカードの場合に、すべてのサブパスがマッチすることを示しました。
このワイルドカードは、パスの途中にも適用され、単一のパスセグメントを表します。他のパスセグメント文字と混在させることはできません。したがって、/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
認可を使用した設定で保護するパスは、すべてテストする必要があります。複数のワイルドカードを使用してパスパターンを記述するのは、手間がかかる場合があります。パスが意図したとおりに認可されていることを確認してください。
次の例では、パスは最も詳細なものから最も詳細でないものの順に並べられています。
最も詳細なパスから最も詳細でないパスの順に並べたリクエストパス /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 つのパスセグメントに完全一致します。
1.1.7. 複数のパスのマッチング: 最も詳細なメソッドが優先される
パスが複数の権限セットに登録されている場合は、リクエストにマッチする 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/foo
は、permit1
のメソッド権限とマッチしません。その結果、deny1
がアクティブになり、リクエストが拒否されます。
1.1.8. 複数のパスとメソッドのマッチング: どちらも優先される
場合に応じて、前述のルールにより、複数の権限セットが同時に適用されることがあります。その場合、リクエストを続行するには、すべての権限でアクセスが許可されている必要があります。これを実現するには、両方にメソッドを追加するか、両方からメソッドを除外する必要があります。メソッド固有のマッチが優先されます。
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
の両方のロールが必要です。
1.1.9. アクセスを拒否するための設定プロパティー
次の設定は、ロールベースのアクセス制御 (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-unannotated-endpoints
と組み合わせることはできません。代わりにdeny
が有効になるためです。 quarkus.security.deny-unannotated-members=true|false
-
true に設定すると、セキュリティーアノテーションを持たないが、セキュリティーアノテーションを持つメソッドを含むクラスで定義されているすべての CDI メソッドと Jakarta REST エンドポイントへのアクセスが拒否されます。デフォルトは
false
です。
1.1.10. 権限の無効化
次のように、宣言された各権限に対して 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
) を使用して実行時に再度有効にできます。
1.1.11. 権限パスと HTTP ルートパス
quarkus.http.root-path
設定プロパティーは、http エンドポイントのコンテキストパス を変更します。
デフォルトでは、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 を参照してください。
1.1.12. 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
パスに関係なく、SecurityIdentity
ロールをデプロイメント固有のロールにマップするだけの場合は、次のようにすることもできます。
quarkus.http.auth.roles-mapping.admin=Admin1 1 2
1.2. アノテーションを使用した認可
Red Hat build of Quarkus には、REST エンドポイントと CDI Bean 上の共通セキュリティーアノテーション @RolesAllowed
、@DenyAll
、@PermitAll
に基づく ロールベースのアクセス制御 (RBAC) を可能にするセキュリティーが組み込まれています。
アノテーションタイプ | 説明 |
---|---|
| どのセキュリティーロールも指定のメソッドを呼び出すことができないことを指定します。 |
| すべてのセキュリティーロールが指定のメソッドを呼び出せることを指定します。
|
| アプリケーション内のメソッドへのアクセスを許可するセキュリティーロールのリストを指定します。 |
|
Red Hat build of Quarkus は、認証されたすべてのユーザーがリソースにアクセスできるようにする |
| 指定されたメソッドを呼び出すことが許可される権限のリストを指定します。 |
|
指定された Jakarta REST エンドポイントへのアクセスを許可する名前付きの |
次の 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
/subject/authenticated
エンドポイントでは、@Authenticated
アノテーションを指定して、認証されたすべてのユーザーを許可します。- 4
/subject/unsecured
エンドポイントは、@PermitAll
アノテーションを指定することにより、認証されていないアクセスを許可します。- 5
- ユーザープリンシパルを取得する呼び出しは、呼び出し元が認証されていない場合は
null
を返し、呼び出し元が認証されている場合はnon-null
を返します。 - 6
/subject/denied
エンドポイントは、@DenyAll
アノテーションを宣言することにより、呼び出し元のユーザーに関係なく、REST メソッドとしての直接アクセスをすべて禁止します。このメソッドは、このクラスの他のメソッドによって内部的に呼び出すことができます。
IO スレッドで標準のセキュリティーアノテーションを使用する予定の場合は、プロアクティブ認証 の情報を確認してください。
@RolesAllowed
アノテーション値は、デフォルト値やネストされたプロパティー式を含む プロパティー式 をサポートします。アノテーションで使用される設定プロパティーは実行時に解決されます。
アノテーション | 値の説明 |
---|---|
|
エンドポイントは、 |
| 値に複数の変数を含めることができることを示す例。 |
|
デフォルト値の例示。必要なロールを、 |
@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
の各ロールがアクセスできるようになります。
1.2.1. エンドポイントセキュリティーアノテーションと 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"; } }
デフォルトのインターフェイスメソッドとして宣言された 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(); } }
1.2.2. 権限のアノテーション
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
を使用する予定の場合は、プロアクティブ認証 の情報を確認してください。
Quarkus インターセプターの制限により、@PermissionsAllowed
をクラスレベルで繰り返すことはできません。詳細は、Quarkus の「CDI reference」ガイドの 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
read
、write
、およびlist
アクションを許可する権限media-library
を付与します。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 REST SecurityContexts では機能しません。jakarta.ws.rs.core.SecurityContext
に権限がないためです。
1.2.2.1. 権限チェッカーを作成する
デフォルトでは、SecurityIdentity
は、この 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); } }
権限チェックは、デフォルトでイベントループで実行します。ワーカースレッドでチェックを実行する場合は、権限チェッカーメソッドに 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.2.2.2. 権限メタアノテーションを作成する
@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
アノテーションインスタンスにより保護されます。
1.2.2.3. @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; } }
以下は、ユーザープリンシパル、カスタムヘッダー、クエリーパラメーターに基づいて 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
クラスがある場合に便利です。