Authorization of web endpoints

Quarkus 整合了一个可插拔 Web 安全层。当安全处于活动状态时,系统会对所有 HTTP 请求执行权限检查以确定请求是否应继续。 如果路径受 quarkus.http.auth. 配置的限制,则使用 @PermitAll 不会打开该路径。要确保可以访问特定路径,必须在 Quarkus 安全设置中进行适当的配置。

如果使用 Jakarta RESTful Web 服务,请考虑使用 quarkus.security.jaxrs.deny-unannotated-endpointsquarkus.security.jaxrs.default-roles-allowed 来设置默认安全要求,而非 HTTP 路径级匹配,因为注释可以覆盖各个端点中的这些属性。

授权基于安全提供程序提供的用户角色。要自定义这些角色,可创建 SecurityIdentityAugmentor,请参阅 Security Identity Customization

Authorization using configuration

在 Quarkus 配置中,权限按权限集定义,每个权限集都指定一项访问控制策略。

Table 1. Quarkus policies summary
Built-in policy Description

deny

此策略拒绝所有用户。

permit

此策略允许所有用户。

authenticated

此策略仅允许已认证用户。

您可以定义基于角色的策略,允许拥有特定角色的用户访问资源。

Example of a role-based policy
quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin                  1
1 这定义一项基于角色的策略,该策略允许拥有 useradmin 角色的用户。

您可以通过配置 application.properties 文件中定义的内置权限集来引用自定义策略,如下面的配置示例中概述的那样:

Example of policy configuration
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 策略,以允许 GET 方法访问 /public。在这种情况下,演示的设置不会影响此示例,因为此请求无论如何都是允许的。
2 此权限引用 /forbidden/forbidden/ 路径的内置 deny 策略。因为它不会以 * 结尾,所以这是一个精确路径匹配。
3 此权限集引用先前定义的策略。roles1 是一个示例名称;您可以按自己的意愿调用权限集。

上例中的确切路径模式 /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/`路径不安全。

Custom HttpSecurityPolicy

有时,注册您自己的命名策略可能很有用。您可以通过创建实现 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy 接口的应用范围 CDI bean 来完成此操作,如下面的示例所示:

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;

@ApplicationScoped
public class CustomNamedHttpSecPolicy implements HttpSecurityPolicy {
    @Override
    public Uni<CheckResult> checkPermission(RoutingContext event, Uni<SecurityIdentity> identity,
            AuthorizationRequestContext requestContext) {
        if (customRequestAuthorization(event)) {
            return Uni.createFrom().item(CheckResult.PERMIT);
        }
        return Uni.createFrom().item(CheckResult.DENY);
    }

    @Override
    public String name() {
        return "custom"; 1
    }

    private static boolean customRequestAuthorization(RoutingContext event) {
        // here comes your own security check
        return !event.request().path().endsWith("denied");
    }
}
1 具有名称的 HTTP 安全策略仅适用于与 application.properties 路径匹配规则匹配的请求。
Example of custom named HttpSecurityPolicy referenced from configuration file
quarkus.http.auth.permission.custom1.paths=/custom/*
quarkus.http.auth.permission.custom1.policy=custom                              1
1 自定义策略名称必须与 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name 方法返回的值匹配。

您还可以在每个请求上创建一个全局的 HttpSecurityPolicy。只需不要实现 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name 方法并将策略保持无名。

Matching on paths and methods

权限集还可以将路径和方法指定为逗号分隔的列表。如果路径以 * 通配符结尾,它生成的查询将匹配所有子路径。否则,它将查询确切匹配并仅匹配该特定路径:

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 路径末尾的 * 通配符与零个或多个路径段匹配,但从不与从 /public 路径开始的任何单词匹配。出于此原因,此模式不匹配 /public-info 这样的路径。

Matching a path but not a method

如果请求基于路径与一个或多个权限集匹配,但没有所需的方法,则将拒绝该请求。

鉴于前面的权限集, GET /public/foo 将同时匹配路径和方法,因此被允许。相比之下, POST /public/foo 将匹配路径但不匹配方法,因此被拒绝。

Matching multiple paths: longest path wins

匹配始终在“最长路径获胜”的基础上进行。如果已经匹配更具体的权限集,则不会考虑不太具体的权限集:

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,请求被拒绝。

子路径权限优先于根路径权限,如先前说明的 deny1permit1 权限示例所示。 当子路径权限允许访问公共资源而根路径权限需要授权时,此规则通过场景得到了进一步举例说明。

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

Matching multiple sub-paths: longest path to the * wildcard wins

前面的示例演示了在路径以 * 通配符结尾时如何匹配所有子路径。

此通配符也适用于路径中间,表示一个单一路径段。它不能与其他路径段字符混合使用;因此,路径分隔符始终包含 wildcard, as seen in the /public//about-us 路径。

当多个路径模式对应于同一请求路径时,系统会选择到 wildcard. In this context, every path segment character is more specific than the 通配符的最长子路径。

这是一个简单的示例:

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 路径更具体,因此任何人都可以访问。

应测试所有使用配置进行授权保护的路径。编写具有多个通配符的路径模式可能会很麻烦。请确保路径已按您的预期进行授权。

在以下示例中,路径是从最具体的路径到最不具体的路径排序的:

Request path /one/two/three/four/five matches ordered from the most specific to the least specific path
/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
/*

放在其他任何位置的 wildcard at the end of the path matches zero or more path segments. The 通配符完全匹配一个路径段。

Matching multiple paths: most specific method wins

当使用多个权限集注册路径时,明确指定与请求匹配的 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 被激活,导致请求被拒绝。

Matching multiple paths and methods: both win

有时,前面描述的规则允许多个权限集同时获胜。在这种情况下,为继续进行请求,所有权限都必须允许访问。要实现此目的,两者都必须指定该方法或不包含任何方法。方法特定匹配优先。

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 将匹配两个权限集的路径,要求同时具有 useradmin 角色。

Configuration properties to deny access

以下配置设置会更改基于角色的访问控制 (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 to false.

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 with deny-unannotated-endpoints because deny 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.

Disabling permissions

权限可以在构建时使用每个声明权限的 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

Permission paths and HTTP root path

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 的值改变时不会调整路径。

Example:
quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt

此配置仅影响从固定或静态 URL 提供的资源 /public,如果 quarkus.http.root-path 已设置为除 / 之外的其他内容,则它可能与您的应用程序资源不匹配。

有关更多信息,请参阅 Path Resolution in Quarkus

Map SecurityIdentity roles

获胜角色型策略可以将 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 将同时具有 adminAdmin1 角色。
2 /* 路径已受保护,只允许授权的 HTTP 请求访问。

如果你需要将 SecurityIdentity 角色映射到特定于部署的角色,而不考虑路径,你还可以执行以下操作:

quarkus.http.auth.roles-mapping.admin=Admin1 1 2
1 admin 角色映射到 Admin1 角色。SecurityIdentity 将同时具有 adminAdmin1 角色。
2 /* 路径未受保护。你必须使用标准安全注释保护你的端点,或者除了此配置属性之外,你还可以定义 HTTP 权限。

Shared permission checks

一个关于未共享权限检查的重要规则是只应用一个路径匹配,即最具体的匹配。自然而然,你可以根据需要指定具有相同获胜路径的众多权限,并且所有权限都将应用。但是,可能有你想应用到许多路径的权限检查,而不必一次又一次地重复它们。这就是共享权限检查发挥作用的地方,当权限路径匹配时,它们始终被应用。

Example of custom named HttpSecurityPolicy applied on every HTTP request
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 Custom HttpSecurityPolicy 也将应用于 /admin/1 路径以及 admin-policy1 策略。

配置许多共享权限检查不如配置未共享权限检查有效。使用共享权限来补充未共享权限检查,就像下面的示例中。

Map SecurityIdentity roles with shared permission
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/ and /secured/admin/ 路径。
2 /secured/* 路径只能由授权的用户访问。通过这种方式,你已保护了 /secured/all 路径等等。
3 共享权限始终在未共享权限之前应用,因此具有 root 角色的 SecurityIdentity 还将具有 user 角色。

Authorization using annotations

Quarkus 包括内置安全功能,以便根据 REST 端点和 CDI bean 上常见的安全注释 @RolesAllowed@DenyAll@PermitAll 来允许 Role-Based Access Control (RBAC)

Table 2. Quarkus annotation types summary
Annotation type Description

@DenyAll

指定不允许任何安全角色调用指定的方法。

@PermitAll

指定允许所有安全角色调用指定的方法。@PermitAll 允许所有人进入,即使未授权。

@RolesAllowed

指定允许访问应用程序中的方法的安全角色列表。等同于 @RolesAllowed("**"),Quarkus 还提供了 io.quarkus.security.Authenticated 注释,该注释允许任何授权用户访问该资源。

下面的 [subject-example] 演示了一个端点,它使用 Jakarta REST 和通用安全注释来描述和保护其端点。

SubjectExposingResource example
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("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") 注释。
2 端点从 Jakarta REST SecurityContext 获取用户主体。这将针对受保护端点返回 non-null
3 /subject/unsecured 端点允许未授权访问,方法是指定 @PermitAll 注释。
4 获取用户主体身份的调用返回 null(如果调用方未经身份验证)和 non-null(如果调用方已经过身份验证)。
5 /subject/denied 端点声明 @DenyAll 注解,禁止对它作为 REST 方法的任何直接访问,无论哪个用户调用它。该方法仍然可以通过此类中的其他方法在内部调用。

如果您计划在 IO 线程上使用标准安全性注解,请查看 Proactive Authentication 中的信息。

@RolesAllowed 注解值支持 property expressions,包括默认值和嵌套属性表达式。与注解一起使用的配置属性在运行时进行解析。

Table 3. Annotation value examples
Annotation Value explanation

@RolesAllowed("${admin-role}")

端点允许具有 admin-role 的用户拥有属性的值所表示的角色.

@RolesAllowed("${tester.group}-${tester.role}")

一个显示该值可以包含多个变量的示例.

@RolesAllowed("${customer:User}")

默认值演示.所需的 role 由 customer 属性的值表示.不过,如果未指定该属性,则需要命名为 User 的 role 为默认值.

Example of a property expressions usage in the @RolesAllowed annotation
admin=Administrator
tester.group=Software
tester.role=Tester
%prod.secured=User
%dev.secured=**
all-roles=Administrator,Software,Tester,User
Subject access control example
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 /subject/software-tester 端点需要已被授予 "Software-Tester" role 的经过身份验证的用户.可以在 role 定义中使用多个表达式.
3 /subject/user 端点需要已被通过使用 @RolesAllowed("${customer:User}") 注解授予 "User" role 的经过身份验证的用户,因为我们并未设置 customer 配置属性.
4 在生产环境中,此 /subject/secured 端点需要具有 User role 的经过身份验证的用户.在开发模式中,它允许任何经过身份验证的用户.
5 属性表达式 all-roles 将被视为集合类型 List,因此,端点将对 role Administrator, Software, TesterUser 予以访问.

Endpoint security annotations and Jakarta REST inheritance

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 子资源定位符无法通过标准安全注释得到保护.安全子资源定位符必须在接口实现中实现和保护,如下面的示例:

@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();
    }
}

Permission annotation

Quarkus 还提供了 io.quarkus.security.PermissionsAllowed 注解,它授权任何具有给定权限的经过身份验证的用户访问资源.此注释是常见安全注释的扩展,并检查授予 SecurityIdentity 实例的权限.

Example of endpoints secured with the @PermissionsAllowed annotation
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 仅对具有 createupdate 权限的用户予以访问.
2 默认情况下,需要通过一个注释实例指定至少一个权限。你可以通过设置 @1 要求所有权限。两个资源方法 @2 具有相同的授权要求。
3 在 @4 具有 @5 权限或 @6 权限且存在 @7 或 @8 操作之一的情况下,将向 @3 授予访问权限。
4 你可以使用你首选的 @9 实现。默认情况下,基于字符串的权限由 @10 执行。
5 权限不是 bean,因此通过编程方式使用 @11 是获得 bean 实例的唯一方法。

如果你计划在 IO 线程上使用 @12,请查阅 @13 中的信息。

@14 由于 Quarkus 拦截器的限制,在类级别上不可重复。有关详细信息,请参阅 Quarkus “CDI 参考”指南的 @15 部分。

向支持角色的 @16 实例添加权限的最简单方式是将角色映射到权限。使用 @19 为认证请求授予 @18 端点的所需 @17 权限,如下例所示:

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 向 @23 角色的 @22 实例添加权限 @20 和操作 @21。类似地,对于 @24 注释,默认情况下使用 @25。
2 权限 @26、@27 和 @28 被映射到角色 @29。
3 角色策略 @30 仅允许认证请求访问 @31 和 @32 子路径。有关路径匹配算法的详细信息,请参见本指南后面的 @33。
4 你可以指定 @34 类的自定义实现。你的自定义类必须定义一个接受权限名称以及一些操作(例如 @35 数组)的构造函数。在此场景中,权限 @36 作为 @38 添加到 @37 实例。

你还可以创建带有附加构造函数参数的自定义 @39 类。这些附加参数与使用 @40 注释的方法的自变量相匹配。随后,Quarkus 使用实际参数实体化你的自定义权限,已调用使用 @41 注释的方法。

Example of a custom java.security.Permission class that accepts additional arguments
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 自定义 @42 类必须只有一个构造函数。第一个参数始终被视为权限名称并且必须为类型 @43。Quarkus 可将权限操作传递给构造函数。为此,将第二个参数声明为 @44。

如果 @46 被允许执行所需的操作之一(例如 @47、@48 或 @49),则 @45 类允许访问当前或父库。

以下示例展示了如何使用 @50 类:

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 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 形式参数 @51 被识别为第一个 @52 参数并且传递给 @53 类。但是,每次调用 @55 方法时都必须实例化 @54。
2 此处,第一个 @56 参数为 @57;因此,@58 参数通过 @59 显式标记。权限构造函数和注释的方法必须设置参数 @60;否则,验证将失败。
Example of a resource secured with the 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);
    }

}

与 @61 示例类似,以下示例展示了如何向具有 @62 角色的用户授予更新 @63 的权限:

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 在构建本机可执行文件时,必须为反射注册权限类,除非它至少在一个 @64 参数中也被使用。有关 @65 注释的更多详细信息可以在 @66 页面上找到。
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 权限,其中允许 readwritelist 操作。因为 MediaLibraryTvLibrary 类的父类,因此,具有 admin 角色的用户还可以修改 TvLibrary

从 Keycloak 提供程序的 Dev UI 页面可以测试 /library/* 路径,因为由 Dev Services for Keycloak 自动创建的用户 alice`具有 `admin 角色。

到目前为止,提供的示例演示了角色对权限的映射。还可以以编程方式向 SecurityIdentity 实例添加权限。在以下示例中,SecurityIdentity is customized 用来添加之前通过基于 HTTP 角色的策略授予的相同的权限。

Example of adding the 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 创建的 media-library 权限可以执行 readwritelist 操作。因为 MediaLibraryTvLibrary 类的父类,因此,具有 admin 角色的用户还可以修改 TvLibrary
2 可以通过 io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder#addPermissionChecker 添加权限检查器。

基于注释的权限不适用于自定义 Jakarta REST SecurityContexts,因为 jakarta.ws.rs.core.SecurityContext 中没有权限。