ウェブエンドポイントの認可
Quarkus has an integrated pluggable web security layer. If security is enabled, all HTTP requests have a permission check performed to verify that they are allowed to continue. Therefore, you cannot use @PermitAll
to open a path if the path is blocked by the quarkus.http.auth.
configuration.
If you use Jakarta RESTful Web Services, consider using |
認可は、セキュリティ・プロバイダーが提供するユーザー・ロールに基づきます。これらのロールをカスタマイズするには、 SecurityIdentityAugmentor
を作成することができます。 セキュリティ・アイデンティティのカスタマイズ を参照してください。
設定を利用した認可
Permissions are defined in the Quarkus configuration using permission sets, each specifying a policy for access control.
組込ポリシー | 説明 |
---|---|
|
このポリシーは、すべてのユーザーを拒否します。 |
|
このポリシーは、すべてのユーザーを許可します。 |
|
このポリシーは、認証されたユーザーのみを許可します。 |
You can define role-based policies that allow users with specific roles to access the resources.
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin (1)
1 | This defines a role-based policy that allows users with the user and admin roles. |
You can reference a custom policy by configuring the built-in permission sets that are defined in the application.properties
file, as outlined in the following configuration example:
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 | This permission references the default built-in permit policy to allow GET methods to /public . In this case, the demonstrated setting would not affect this example because this request is allowed anyway. |
2 | This permission references the built-in deny policy for /forbidden . It is an exact path match because it does not end with * . |
3 | This permission set references the previously defined policy. roles1 is an example name; you can call the permission sets whatever you want. |
パスとメソッドのマッチング
Permission sets can also specify paths and methods as a comma-separated list. If a path ends with the *
wildcard, the query it generates matches all sub-paths. Otherwise, it queries for an exact match and only matches that specific path:
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
パスはマッチするがメソッドはマッチしない場合
The request is rejected if it matches one or more permission sets based on the path but none of the required methods.
Given the preceding permission set, GET /public/foo would match both the path and method and therefore be allowed. In contrast, POST /public/foo would match the path but not the method. It would therefore be rejected.
|
複数のパスのマッチング:一番長いパスが勝ちます。
マッチングは常にロンゲストパス に基づいて行われ、より具体的なパーミッションセットがマッチした場合、それより具体的でないパーミッションセットは考慮されません。:
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
Given the preceding permission set, GET /public/forbidden-folder/foo would match both permission sets' paths. However, because the longer path matches the path of the deny1 permission set, deny1 is chosen, and the request is rejected.
|
Subpath permissions always win against the root path permissions, as explained in the preceding
|
複数のパスのマッチング:一番具体的なパスが勝ちます
When a path is registered with multiple permission sets, the permission sets explicitly specifying an HTTP method that matches the request take precedence. In this instance, the permission sets without methods only come into effect if the request method does not match permission sets with the method specification.
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
Given the preceding permission set, Conversely, |
複数のパスのマッチング:一番長いパスが勝ちます
Sometimes, the previously described rules allow multiple permission sets to win simultaneously. In that case, for the request to proceed, all the permissions must allow access. For this to happen, both must either have specified the method or have no method. Method-specific matches take precedence.
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
Given the preceding permission set, GET /api/foo would match both permission sets' paths, requiring both the user and admin roles.
|
アクセスを拒否するための設定プロパティ
以下の設定により、ロールベースアクセスコントロール(RBAC)の拒否動作が変更されます:
quarkus.security.jaxrs.deny-unannotated-endpoints=true|false
-
If set to true, access is denied for all Jakarta REST endpoints by default. If a Jakarta REST endpoint has no security annotations, it defaults to the
@DenyAll
behavior. This helps you to avoid accidentally exposing an endpoint that is supposed to be secured. Defaults tofalse
. quarkus.security.jaxrs.default-roles-allowed=role1,role2
-
Defines the default role requirements for unannotated endpoints. The
**
role is a special role that means any authenticated user. This cannot be combined withdeny-unannotated-endpoints
becausedeny
takes effect instead. quarkus.security.deny-unannotated-members=true|false
-
If set to true, the access is denied to all CDI methods and Jakarta REST endpoints that do not have security annotations but are defined in classes that contain methods with security annotations. Defaults to
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ルートパス
The quarkus.http.root-path
configuration property changes the 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
A leading slash changes how the configured permission path is interpreted. The configured URL is used as-is, and paths are not adjusted if the value of quarkus.http.root-path
changes. For example:
quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt
This configuration only impacts resources served from the fixed or static URL, /public
, which might not match your application resources if quarkus.http.root-path
has been set to something other than /
.
For more information, see Path Resolution in Quarkus.
アノテーションを使った認可
Quarkus includes built-in security to allow for Role-Based Access Control (RBAC) based on the common security annotations @RolesAllowed
, @DenyAll
, @PermitAll
on REST endpoints and CDI beans.
アノテーション型 | 説明 |
---|---|
@DenyAll |
どのセキュリティロールも指定されたメソッドを呼び出すことを許可されていないことを指定します。 |
@PermitAll |
Specifies that all security roles are allowed to invoke the specified methods.
|
@RolesAllowed |
アプリケーション内のメソッドへのアクセスが許可されているセキュリティロールのリストを指定します。
|
The following SubjectExposingResource example demonstrates an endpoint that uses both Jakarta REST and Common Security annotations to describe and secure its endpoints.
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("unsecured")
@PermitAll (3)
public String getSubjectUnsecured(@Context SecurityContext sec) {
Principal user = sec.getUserPrincipal(); (4)
String name = user != null ? user.getName() : "anonymous";
return name;
}
@GET
@Path("denied")
@DenyAll (5)
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 | The endpoint obtains the user principal from the Jakarta REST SecurityContext . This returns non-null for a secured endpoint. |
3 | /subject/unsecured エンドポイントでは、 @PermitAll アノテーションを指定することで、認証されていないアクセスが可能になります。 |
4 | The call to obtain the user principal returns null if the caller is unauthenticated and non-null if the caller is authenticated. |
5 | The /subject/denied endpoint declares the @DenyAll annotation, disallowing all direct access to it as a REST method, regardless of the user calling it. The method is still invokable internally by other methods in this class. |
If you plan to use standard security annotations on the IO thread, review the information in Proactive Authentication. |
The @RolesAllowed
annotation value supports property expressions including default values and nested property expressions. Configuration properties used with the annotation are resolved at runtime.
アノテーション | 値の説明 |
---|---|
|
The endpoint allows users with the role denoted by the value of the |
|
値が複数の変数を含むことができることを示す例。 |
|
A default value demonstration.
The required role is denoted by the value of the |
@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.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("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 | This /subject/software-tester endpoint requires an authenticated user that has been granted the role of "Software-Tester". It is possible to use multiple expressions in the role definition. |
3 | This /subject/user endpoint requires an authenticated user that has been granted the role "User" through the use of the @RolesAllowed("${customer:User}") annotation because we did not set the configuration property customer . |
4 | In production, this /subject/secured endpoint requires an authenticated user with the User role. In development mode, it allows any authenticated user. |
5 | Property expression all-roles will be treated as a collection type List , therefore the endpoint will be accessible for roles Administrator , Software , Tester and User . |
パーミッションアノテーション
Quarkus also provides the io.quarkus.security.PermissionsAllowed
annotation, which authorizes any authenticated user with the given permission to access the resource. This annotation is an extension of the common security annotations and checks the permissions granted to a SecurityIdentity
instance.
@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 | The resource method createOrUpdate is only accessible for a user with both create and update permissions. |
2 | By default, at least one of the permissions specified through one annotation instance is required. You can require all permissions by setting inclusive=true . Both resource methods createOrUpdate have equal authorization requirements. |
3 | Access is granted to getItem if SecurityIdentity has either read permission or see permission and one of the all or detail actions. |
4 | You can use your preferred java.security.Permission implementation. By default, string-based permission is performed by io.quarkus.security.StringPermission . |
5 | Permissions are not beans, therefore the only way to obtain bean instances is programmatically by using Arc.container() . |
If you plan to use the @PermissionsAllowed on the IO thread, review the information in Proactive Authentication.
|
@PermissionsAllowed is not repeatable on the class level due to a limitation with Quarkus interceptors. For more information, see the Repeatable interceptor bindings section of the Quarkus "CDI reference" guide.
|
The easiest way to add permissions to a role-enabled SecurityIdentity
instance is to map roles to permissions. Use Authorization using configuration to grant the required SecurityIdentity
permissions for CRUDResource
endpoints to authenticated requests, as outlined in the following example:
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 | Add the permission see and the action all to the SecurityIdentity instance of the user role. Similarly, for the @PermissionsAllowed annotation, io.quarkus.security.StringPermission is used by default. |
2 | Permissions create , update , and read are mapped to the role admin . |
3 | The role policy role-policy1 allows only authenticated requests to access /crud/modify and /crud/id sub-paths. For more information about the path-matching algorithm, see Matching multiple paths: longest path wins later in this guide. |
4 | You can also specify a custom implementation of the java.security.Permission class. Your custom class must define exactly one constructor that accepts the permission name and optionally some actions, for example, String array. In this scenario, the permission list is added to the SecurityIdentity instance as new CustomPermission("list") . |
You can also create a custom java.security.Permission
class with additional constructor parameters. These additional parameters get matched with arguments 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
class that accepts additional argumentsimport 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;
}
...
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 {
...
}
}
1 | There must be exactly one constructor of a custom Permission class. The first parameter is always considered to be a permission name and must be of type String . Quarkus can optionally pass permission actions to the constructor. For this to happen, declare the second parameter as String[] . |
The LibraryPermission
class permits access to the current or parent library if SecurityIdentity
is allowed to perform one of the required actions, for example, read
, write
, or list
.
The following example shows how the LibraryPermission
class can be used:
import io.quarkus.security.PermissionsAllowed;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class LibraryService {
@PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class) (1)
public Library updateLibrary(String newDesc, Library update) {
update.description = newDesc;
return update;
}
@PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class, params = "library") (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 update is identified as the first Library parameter and gets passed to the LibraryPermission class. However, the LibraryPermission must be instantiated each time the updateLibrary method is invoked. |
2 | Here, the first Library parameter is migrate , therefore the library parameter gets marked explicitly through PermissionsAllowed#params . The permission constructor and the annotated method must have the parameter library set, otherwise, validation fails. |
LibraryPermission
@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);
}
}
Similarly to the CRUDResource
example, the following example shows how you can grant a user with the admin
role permissions to update MediaLibrary
:
package org.acme.library;
import java.security.Permission;
import java.util.Arrays;
import java.util.Set;
public class MediaLibraryPermission extends LibraryPermission {
public MediaLibraryPermission(String libraryName, String[] actions) {
super(libraryName, actions, new MediaLibrary()); (1)
}
}
1 | We want to pass the MediaLibrary instance to the LibraryPermission constructor. |
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 | Grants the permission media-library , which permits read , write , and list actions. Because MediaLibrary is the TvLibrary class parent, a user with the admin role is also permitted to modify TvLibrary . |
The examples provided so far use role-to-permission mapping. You can also add permissions to the SecurityIdentity
instance programmatically. In the following example, SecurityIdentity
is customized to add the same permission that was previously granted with the HTTP role-based policy.
LibraryPermission
programmatically to SecurityIdentity
import java.security.Permission;
import java.util.function.Function;
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) {
Permission possessedPermission = new MediaLibraryPermission("media-library",
new String[] { "read", "write", "list"}); (1)
return QuarkusSecurityIdentity.builder(identity)
.addPermissionChecker(new Function<Permission, Uni<Boolean>>() { (2)
@Override
public Uni<Boolean> apply(Permission requiredPermission) {
boolean accessGranted = possessedPermission.implies(requiredPermission);
return Uni.createFrom().item(accessGranted);
}
})
.build();
}
}
1 | The permission media-library 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 . |
2 | You can add a permission checker through io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder#addPermissionChecker . |
Annotation permissions do not work with the custom Custom Jakarta REST SecurityContext because there are no permissions in jakarta.ws.rs.core.SecurityContext .
|