Quarkus has an integrated pluggable web security layer. If security is enabled, all HTTP requests will have a permission check performed to make sure they are allowed to continue.
This means you cannot use @PermitAll
to open a path if the path is blocked by the quarkus.http.auth.
configuration.
Note
|
If you are using Jakarta REST, consider using |
Authorization is based on user roles that the security provider provides.
To customize these roles, a SecurityIdentityAugmentor
can be created, see
Security Identity Customization.
Permissions are defined in the Quarkus configuration using permission sets, with each permission set specifying a policy for access control.
Built-in policy |
Description |
|
This policy denies all users. |
|
This policy permits all users. |
|
This policy permits only authenticated users. |
You can define role-based policies that allow users with specified roles to access the resources.
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin (1)
-
This defines a role-based policy that allows users with the
user
andadmin
roles. Such a custom policy can be referenced by permission sets just like the built-in ones, as shown in the example below.
Permission sets are defined in application.properties
as follows:
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
-
This permission references the default
permit
built-in policy to allowGET
methods to/public
. In this case, the demonstrated setting would not affect this example because this request is allowed anyway. -
This permission references the built-in
deny
policy for/forbidden
. This is an exact path match as it does not end with*
. -
This is a permission set that 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 will only match 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 a request matches one or more permission sets based on the path but does not match any due to method requirements.
Tip
|
Given the above permission set, GET /public/foo would match both the path and method and thus be allowed,
whereas POST /public/foo would match the path but not the method and would therefore be rejected.
|
Matching is always done on the "longest path wins" basis. Less specific permission sets are not considered if a more specific one has been matched:
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
Tip
|
Given the above permission set, GET /public/forbidden-folder/foo would match both permission sets' paths,
but because it matches the deny1 permission set’s path on a longer match, deny1 will be chosen, and the request will
be rejected.
|
Note
|
Subpath permissions always win against the root path permissions, as explained above in the 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 |
When a path is registered with multiple permission sets, the permission sets that explicitly specify an HTTP method that matches the request will take precedence. In this instance, the permission sets without methods will 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
Note
|
Given the above permission set,
|
Sometimes, the previously described rules allow multiple permission sets to win at the same time. In that case, for the request to proceed, all the permissions must allow access. Note that for this to happen, both have to 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
Tip
|
Given the above permission set, GET /api/foo would match both permission sets' paths, requiring both the user and admin roles.
|
The following configuration settings alter the role-based access control (RBAC) denying behavior:
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 does not have any security annotations, it defaults to the
@DenyAll
behavior. This is useful to ensure you cannot accidentally expose 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
, asdeny
takes the effect instead. quarkus.security.deny-unannotated-members=true|false
-
-
if set to true, the access will be 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
.
-
Permissions can be disabled at build time with an enabled
property for each declared permission, such as:
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
Permissions can be reenabled at runtime with a system property or environment variable, such as:
-Dquarkus.http.auth.permission.permit1.enabled=true
.
The quarkus.http.root-path
configuration property is used to change the http endpoint context path.
By default, quarkus.http.root-path
is prepended automatically to configured permission paths then do not use a forward slash, for example:
quarkus.http.auth.permission.permit1.paths=public/*,css/*,js/*,robots.txt
This configuration is equivalent to the following:
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 will change how the configured permission path is interpreted.
The configured URL will be used as-is, and paths will not be adjusted if the value of quarkus.http.root-path
is changed.
For example:
quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt
This configuration will only impact resources served from the fixed/static URL /public
, which may not match your application resources if quarkus.http.root-path
has been set to something other than /
.
See Path Resolution in Quarkus for more information.
{project-name} comes with 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.
Annotation type |
Description |
@DenyAll |
Specifies that no security roles are allowed to invoke the specified methods. |
@PermitAll |
Specifies that all security roles are allowed to invoke the specified methods.
|
@RolesAllowed |
Specifies the list of security roles permitted to access methods in an application. As an equivalent to |
SubjectExposingResource example featured in this chapter 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;
}
}
-
The
/subject/secured
endpoint requires an authenticated user with the granted "Tester" role through the use of the@RolesAllowed("Tester")
annotation. -
The endpoint obtains the user principal from the Jakarta REST
SecurityContext
. This will be non-null for a secured endpoint. -
The
/subject/unsecured
endpoint allows for unauthenticated access by specifying the@PermitAll
annotation. -
The call to obtain the user principal returns null if the caller is unauthenticated and non-null if the caller is authenticated.
-
The
/subject/denied
endpoint declares the@DenyAll
annotation, thus 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.
Caution
|
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.
Annotation |
Value explanation |
|
The endpoint will allow users with the role denoted by the value of the |
|
An example showing that the value can contain multiple variables. |
|
A default value demonstration.
The required role will be denoted by the value of the |
@RolesAllowed
annotationadmin=Administrator
tester.group=Software
tester.role=Tester
%prod.secured=User
%dev.secured=**
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) {
Principal user = sec.getUserPrincipal();
String name = user != null ? user.getName() : "anonymous";
return name;
}
@GET
@Path("software-tester")
@RolesAllowed("${tester.group}-${tester.role}") (2)
public String getSubjectSoftwareTester(@Context SecurityContext sec) {
Principal user = sec.getUserPrincipal();
String name = user != null ? user.getName() : "anonymous";
return name;
}
@GET
@Path("user")
@RolesAllowed("${customer:User}") (3)
public String getSubjectUser(@Context SecurityContext sec) {
Principal user = sec.getUserPrincipal();
String name = user != null ? user.getName() : "anonymous";
return name;
}
@GET
@Path("secured")
@RolesAllowed("${secured}") (4)
public String getSubjectSecured(@Context SecurityContext sec) {
Principal user = sec.getUserPrincipal();
String name = user != null ? user.getName() : "anonymous";
return name;
}
}
-
The
@RolesAllowed
annotation value is set to the value ofAdministrator
. -
This
/subject/software-tester
endpoint requires an authenticated user that has been granted the role "Software-Tester". It is possible to use multiple expressions in the role definition. -
This
/subject/user
endpoint requires an authenticated user that has been granted the role "User" through the use of the@RolesAllowed("${customer:User}")
annotation, as we did not set the configuration propertycustomer
. -
This
/subject/secured
endpoint requires an authenticated user that has been granted the roleUser
in production but allows any authenticated user in development mode.
Quarkus also provides the io.quarkus.security.PermissionsAllowed
annotation that will permit any authenticated user with given permission to access the resource.
The annotation is extension of the common security annotations and check permissions granted to SecurityIdentity
.
@PermissionsAllowed
annotationpackage 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;
}
}
}
-
Resource method
createOrUpdate
is only accessible by user with bothcreate
andupdate
permissions. -
By default, at least one of the permissions specified through one annotation instance is required. You can require all of them by setting
inclusive=true
. Both resource methodscreateOrUpdate
have equal authorization requirements. -
Access is granted to
getItem
ifSecurityIdentity
has eitherread
permission orsee
permission and one of actions (all
,detail
). -
You can use any
java.security.Permission
implementation of your choice. By default, string-based permission is performed by theio.quarkus.security.StringPermission
. -
Permissions are not beans, therefore only way to obtain bean instances is programmatically via the
Arc.container()
.
Caution
|
If you plan to use the @PermissionsAllowed on the IO thread, review the information in Proactive Authentication.
|
Note
|
The @PermissionsAllowed is not repeatable on class-level due to limitations of Quarkus interceptors.
Please find well-argued explanation in the Repeatable interceptor bindings section of the CDI reference.
|
Provided SecurityIdentity
contains roles, the easiest to add permissions to SecurityIdentity
is to create a roles to permissions mapping.
HTTP role-based policies can be used to grant SecurityIdentity
permissions required by CRUDResource
endpoints to authenticated requests like this:
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
-
Add permission
see
with actionall
toSecurityIdentity
that holds roleuser
. Similarly as for@PermissionsAllowed
annotation,io.quarkus.security.StringPermission
is used by default. -
Permissions
create
,update
andread
are mapped to the roleadmin
. -
The role policy
role-policy1
only allows authenticated requests to access/crud/modify
and/crud/id
sub-paths. Please see Matching multiple paths section of this guide for more information on path matching algorithm. -
You can also specify your own implementation of the
java.security.Permission
class. The class you provide must define exactly one constructor that accepts permission name and optionally actions (asString
array). Here, permissionlist
will be added toSecurityIdentity
asnew CustomPermission("list")
.
You can also create a custom java.security.Permission
with additional constructor parameters.
These additional parameters will be matched with arguments of the method annotated with the @PermissionsAllowed
annotation.
Later, Quarkus will instantiate your custom Permission with actual arguments, with which the method annotated with the @PermissionsAllowed
has been invoked.
java.security.Permission
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 {
...
}
}
-
There must be exactly one constructor of a custom
Permission
class, also first parameter is always considered a permission name (must beString
). Optionally, Quarkus may pass Permission actions to the constructor. Just declare the second parameter asString[]
.
The LibraryPermission
permit access to a library if SecurityIdentity
is allowed to perform one of required actions
(like read
, write
, list
) on the very same library, or the parent one. Let’s see how it is 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;
}
}
-
Formal parameter
update
is identified as the firstLibrary
parameter and passed to theLibraryPermission
. However this option comes with a price, as theLibraryPermission
must be instantiated every single timeupdateLibrary
method is invoked. -
Here, the first
Library
parameter ismigrate
, therefore we markedlibrary
parameter explicitly viaPermissionsAllowed#params
. Please note that both Permission constructor and annotated method must have parameterlibrary
, otherwise validation will fail.
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, we can use permission to role mapping and grant user with role admin
right 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)
}
}
-
We want to pass
MediaLibrary
instance to theLibraryPermission
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
-
Grants permission
media-library
that is allowed to perform actionsread
,write
andlist
. ConsideringMediaLibrary
is theTvLibrary
class parent, we know that administrator is also going to be allowed to modify television library.
All the examples above leveraged role to permission mapping, but you can also add permissions to the SecurityIdentity
programmatically.
In the example below, we use Security Identity Customization to add
the same permission as we previously granted with the HTTP role-based policy.
LibraryPermission
programmatically to the 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();
}
}
-
Created permission
media-library
is allowed to perform actionsread
,write
andlist
. ConsideringMediaLibrary
is theTvLibrary
class parent, we know that administrator is also going to be allowed to modify television library. -
You can add a permission checker via
io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder#addPermissionChecker
.
Caution
|
Annotation permissions do not work with the custom JAX-RS SecurityContext, for there are no permissions in jakarta.ws.rs.core.SecurityContext .
|