Web 端点授权


Red Hat build of Quarkus 3.8

Red Hat Customer Content Services

摘要

本指南探索 Web 端点的授权机制,专注于基于配置和基于注解的方法。它检查配置授权,包括使用内置和自定义策略、路径和方法匹配以及处理复杂路径场景。然后,它被分成基于角色的访问控制和权限管理的细微,涵盖了访问拒绝、禁用权限并将角色映射到 SecurityIdentity 的属性。介绍了使用注解保护 RESTful 服务、突出显示标准安全注解及其应用程序的信息。

提供有关红帽构建的 Quarkus 文档的反馈

要报告错误或改进文档,请登录到 Red Hat JIRA 帐户并提交问题。如果您没有 Red Hat Jira 帐户,则会提示您创建一个帐户。

流程

  1. 单击以下链接 以创建 ticket
  2. Summary 中输入问题的简短描述。
  3. Description 中提供问题或功能增强的详细描述。包括一个指向文档中问题的 URL。
  4. Submit 创建问题,并将问题路由到适当的文档团队。

使开源包含更多

红帽致力于替换我们的代码、文档和 Web 属性中有问题的语言。我们从这四个术语开始:master、slave、黑名单和白名单。由于此项工作十分艰巨,这些更改将在即将推出的几个发行版本中逐步实施。详情请查看 CTO Chris Wright 的信息

第 1 章 Web 端点授权

Quarkus 融合了一个可插拔的 Web 安全层。当安全性处于活动状态时,系统会对所有 HTTP 请求执行权限检查,以确定它们是否应该继续。

如果路径受 quarkus.http.auth. 配置限制,则使用 @PermitAll 将不会打开路径。为确保可以访问特定的路径,必须在 Quarkus 安全设置中进行适当的配置。

注意

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

授权基于安全提供程序提供的用户角色。要自定义这些角色,可以创建一个 SecurityIdentityAugmentor,请参阅 安全身份自定义

1.1. 使用配置授权

权限通过权限集在 Quarkus 配置中定义,每个都指定了访问控制的策略。

Expand
表 1.1. Red Hat build of Quarkus 策略概述
内置策略描述

deny

此策略拒绝所有用户。

permit

此策略允许所有用户。

已验证

此策略仅允许经过身份验证的用户。

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

基于角色的策略示例

quarkus.http.auth.policy.role-policy1.roles-allowed=user,admin                  
1
Copy to Clipboard Toggle word wrap

1
这将定义一个基于角色的策略,允许用户使用 用户和 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
Copy to Clipboard Toggle word wrap

1
此权限引用了默认的内置 允许 策略,以允许 GET 方法访问 /public。在这种情况下,演示的设置不会影响这个示例,因为允许此请求。
2
此权限引用 /forbidden 的内置 deny 策略。它是精确的路径匹配,因为它不以 * 结尾。
3
此权限集引用之前定义的策略。roles1 是一个示例名称,您可以调用您想要的权限集。
警告

示例中的确切路径 /forbidden 将不会保护 /forbidden/ 路径。为 /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 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");
    }
}
Copy to Clipboard Toggle word wrap
1
命名 HTTP 安全策略将仅应用于与 application.properties 路径匹配规则匹配的请求。

从配置文件引用的名为 HttpSecurityPolicy 的自定义示例

quarkus.http.auth.permission.custom1.paths=/custom/*
quarkus.http.auth.permission.custom1.policy=custom                              
1
Copy to Clipboard Toggle word wrap

1
自定义策略名称必须与 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name 方法返回的值匹配。
提示

您还可以在每个请求上创建全局 HttpSecurityPolicy 调用。只需实施 io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy.name 方法,并将策略名称留空。

1.1.2. 匹配路径和方法

权限集也可以将路径和方法指定为用逗号分开的列表。如果路径以 * 通配符结尾,则查询会生成与所有子路径匹配。否则,它会查询完全匹配,且只与该特定路径匹配:

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
Copy to Clipboard Toggle word wrap
1
路径末尾的 * 通配符匹配零个或多个路径片段,但从 /public 路径开始的任何词语。因此,/public-info 等路径与此模式不匹配。

1.1.3. 匹配路径而不是方法

如果请求根据路径匹配一个或多个权限集,但不需要的方法,则请求将被拒绝。

提示

根据前面的权限集,GET /public/foo 将匹配路径和方法,因此被允许。相反,POST /public/foo 将匹配路径,而不是方法,因此被拒绝。

1.1.4. 匹配多个路径:路径的最成功

匹配始终以"最长路径优先"为基础完成。如果一个更具体的权限集匹配,则不会被考虑:

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
Copy to Clipboard Toggle word wrap
提示

根据前面的权限集,GET /public/forbidden-folder/foo 将同时匹配这两个权限集的路径。但是,由于较长的路径与 deny1 权限集的路径匹配,因此选择了 deny1,请求将被拒绝。

注意

subPath 权限之前带有 root 路径权限,因为 deny1permit1 权限示例之前演示了。

此规则通过一个场景进一步说明,其中 subpath 权限允许访问公共资源,而 root 路径权限需要授权。

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
Copy to Clipboard Toggle word wrap

在以前的版本中,当一个路径使用 * 通配符结束时,会演示与所有子路径匹配的示例。

此通配符也适用于代表单一路径段的路径中。它不能与其他路径段字符混合;因此,路径分隔符始终将 * 通配符括起,如 /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
Copy to Clipboard Toggle word wrap
1
请求路径(如 /api/product/detail )只能被经过身份验证的用户访问。
2
路径 /api/public-product/detail 更为具体,因此任何人都可以访问。
重要

应该测试使用配置通过授权保护的所有路径。使用多个通配符编写路径模式可能很繁琐。请确保路径已如预期授权。

在以下示例中,路径从最特定于最具体的路径排序:

请求路径 /one/two/three/four/five 匹配从最特定于最具体的路径排序

/one/two/three/four/five
/one/two/three/four/*
/one/two/three/*/five
/one/two/three/*/*
/one/two/*/four/five
/one/*/three/four/five
/*/two/three/four/five
/*/two/three/*/five
/*
Copy to Clipboard Toggle word wrap

重要

路径末尾的 * 通配符与零个或多个路径片段匹配。* 通配符放置在其它任何位置与一个路径片段匹配。

1.1.6. 匹配多个路径:最具体的方法胜出

当使用多个权限集注册路径时,权限集会明确指定与请求匹配的 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
Copy to Clipboard Toggle word wrap
注意

前面的权限集显示 GET /public/foo 与这两个权限集的路径匹配。如何,它特别符合 permit1 权限集的显式方法。选择 permit1,并接受请求。

相反,PUT /public/foo 与 allow 1 的方法不匹配。因此,会激活 deny1,从而导致请求拒绝。

1.1.7. 匹配多个路径和方法:它们都成功

有时,前面描述的规则允许同时有多个权限集 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
Copy to Clipboard Toggle word wrap
提示

根据前面的权限集,GET /api/foo 将匹配权限集的路径,这需要 用户和 admin 角色。

1.1.8. 拒绝访问的配置属性

以下配置设置更改基于角色的访问控制(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.9. 禁用权限

构建时可以禁用权限,每个声明的权限 都启用了 属性,例如:

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
Copy to Clipboard Toggle word wrap

使用系统属性或环境变量在运行时可以重新启用权限,例如: -Dquarkus.http.auth.permission.permit1.enabled=true

1.1.10. 权限路径和 HTTP 根路径

quarkus.http.root-path 配置属性更改 http 端点上下文路径

默认情况下,quarkus.http.root-path 会自动添加到配置的权限路径前,然后不要使用正斜杠,例如:

quarkus.http.auth.permission.permit1.paths=public/*,css/*,js/*,robots.txt
Copy to Clipboard Toggle word wrap

此配置等同于以下内容:

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
Copy to Clipboard Toggle word wrap

前面斜杠会改变配置的权限路径的解释方式。配置的 URL 用于按原样使用,如果 quarkus.http.root-path 的值发生了变化,则不会调整路径。

Example:

quarkus.http.auth.permission.permit1.paths=/public/*,css/*,js/*,robots.txt
Copy to Clipboard Toggle word wrap

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

如需更多信息,请参阅 Quarkus 中的路径解析

1.1.11. 映射 SecurityIdentity 角色

获奖基于角色的策略可以将 SecurityIdentity 角色映射到特定于部署的角色。然后,这些角色适用于使用 @RolesAllowed 注释的端点授权。

quarkus.http.auth.policy.admin-policy1.roles.admin=Admin1 
1

quarkus.http.auth.permission.roles1.paths=/*
quarkus.http.auth.permission.roles1.policy=admin-policy1
Copy to Clipboard Toggle word wrap
1
admin 角色映射到 Admin1 角色。SecurityIdentity 将同时具有 adminAdmin1 角色。

1.1.12. 共享权限检查

用于未共享权限检查的一个重要规则是仅应用一个路径匹配,它是最具体的。自然,您可以使用与您想要相同的获奖路径来指定多个权限,并全部应用它们。但是,有可能有权限检查,您希望应用到许多路径,而无需重复它们,然后再次重复它们。这是共享权限检查所在的位置,当权限路径匹配时,它们总是被应用。

每个 HTTP 请求中应用名为 HttpSecurityPolicy 的自定义示例

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
Copy to Clipboard Toggle word wrap

1
自定义 HttpSecurityPolicy 也会与 admin-policy1 策略一起应用到 /admin/1 路径。
提示

配置许多共享权限检查比配置不共享权限检查更有效。使用共享权限补充下例中的未共享权限检查。

使用共享权限映射 SecurityIdentity 角色

quarkus.http.auth.policy.role-policy1.roles.root=admin,user 
1

quarkus.http.auth.permission.roles1.paths=/secured/*        
2

quarkus.http.auth.permission.roles1.policy=role-policy1
quarkus.http.auth.permission.roles1.shared=true

quarkus.http.auth.policy.role-policy2.roles-allowed=user    
3

quarkus.http.auth.permission.roles2.paths=/secured/user/*
quarkus.http.auth.permission.roles2.policy=role-policy2

quarkus.http.auth.policy.role-policy3.roles-allowed=admin
quarkus.http.auth.permission.roles3.paths=/secured/admin/*
quarkus.http.auth.permission.roles3.policy=role-policy3
Copy to Clipboard Toggle word wrap

1
角色 root 可以访问 /secured/user8:0:1::/secured/admin prerequisites 路径。
2
/secured Attr 路径只能被经过身份验证的用户访问。这样,您已保护了 /secured/all 路径,以此类推。
3
共享权限始终会在不共享前应用,因此具有 root 角色的 SecurityIdentity 也具有用户角色。

1.2. 使用注解的授权

红帽构建的 Quarkus 包含内置的安全性,允许基于通用安全注解 @RolesAllowed@DenyAll@PermitAll on REST 端点和 CDI Bean 进行 基于角色的访问控制(RBAC)

Expand
表 1.2. Red Hat build of Quarkus 注解类型概述
注解类型描述

@DenyAll

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

@PermitAll

指定允许所有安全角色调用指定方法。

@PermitAll 允许其中的任何人,即使没有身份验证。

@RolesAllowed

指定允许访问应用中的方法的安全角色列表。

作为等同于 @RolesAllowed ("**"),红帽构建的 Quarkus 还提供 io.quarkus.security.Authenticated 注解,允许任何经过身份验证的用户访问该资源。

以下 SubjectExposingResource 示例演示了 一个端点,它使用 Jakarta REST 和 Common Security 注解来描述和保护其端点。

SubjectExposingResource 示例

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;
    }
}
Copy to Clipboard Toggle word wrap

1
/subject/secured 端点需要一个经过身份验证的用户,并通过使用 @RolesAllowed ("Tester") 注解来赋予"Tester"角色。
2
端点从 Jakarta REST SecurityContext 获取用户主体。这会为安全端点返回 非null
3
/subject/unsecured 端点通过指定 @PermitAll 注释来允许未经身份验证的访问。
4
如果调用者通过身份验证,则获取用户主体的调用会返回 null,如果调用者是未经身份验证的且 非null
5
/subject/denied 端点声明 @DenyAll 注释,不允许所有直接访问它作为 REST 方法,无论用户调用它。这个方法仍然可以通过此类中的其他方法在内部调用。
Important

如果您计划在 IO 线程上使用标准安全注解,请查看 主动身份验证 中的信息。

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

Expand
表 1.3. 注解值示例
注解值解释

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

端点允许用户具有由 admin-role 属性的值表示的角色。

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

显示值可以包含多个变量的示例。

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

默认值演示。所需的角色由 customer 属性的值表示。但是,如果没有指定该属性,则需要名为 User 的角色作为默认值。

@RolesAllowed 注释中的属性表达式用法示例

admin=Administrator
tester.group=Software
tester.role=Tester
%prod.secured=User
%dev.secured=**
all-roles=Administrator,Software,Tester,User
Copy to Clipboard Toggle word wrap

主题访问控制示例

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;
    }
}
Copy to Clipboard Toggle word wrap

1
@RolesAllowed 注释值设为 Administrator 的值。
2
这个 /subject/software-tester 端点需要一个经过身份验证的用户,它被授予了 "Software-Tester" 角色。可以在角色定义中使用多个表达式。
3
/subject/user 端点需要一个经过身份验证的用户,它已通过使用 @RolesAllowed ("${customer:User}") 注解授予角色"User",因为我们没有设置配置属性 客户
4
在生产环境中,这个 /subject/secured 端点需要一个具有 User 角色的经过身份验证的用户。在开发模式中,它允许任何经过身份验证的用户。
5
属性表达式 all-roles 将被视为集合类型列表,因此角色 管理员软件测试 程序和用户可以访问端点。

1.2.1. 权限注解

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;
        }
    }
}
Copy to Clipboard Toggle word wrap

1
资源方法 createOrUpdate 只能供具有 createupdate 权限的用户访问。
2
默认情况下,至少需要一个注解实例指定的权限之一。您可以通过设置 inclusive=true 来要求所有权限。这两种资源方法都 createOrUpdate 具有相同的授权要求。
3
如果 SecurityIdentity 具有读取权限 或查看 权限以及 alldetail 操作之一,则访问权限会被授予 getItem
4
您可以使用您首选的 java.security.Permission 实现。默认情况下,基于字符串的权限由 io.quarkus.security.StringPermission 执行。
5
权限不是 Bean,因此使用 Arc.container () 以编程方式获取 bean 实例。
Important

如果您计划在 IO 线程上使用 @PermissionsAllowed,请查看 主动身份验证 中的信息。

注意

由于 Quarkus 拦截器的限制,在类级别上不可重复 @PermissionsAllowed。如需更多信息,请参阅 Quarkus "CDI 参考" 指南中的 Repeatable interceptor bindings 部分。

将权限添加到启用了角色的 SecurityIdentity 实例的最简单方法是将角色映射到权限。使用带有配置的 AuthorizationCRUDResource 端点所需的 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
Copy to Clipboard Toggle word wrap
1
将权限 see操作 添加到 用户角色的 SecurityIdentity 实例。同样,对于 @PermissionsAllowed 注释,默认为使用 io.quarkus.security.StringPermission
2
权限 创建updateread 映射到角色 admin
3
角色策略 role-policy1 只允许经过身份验证的用户访问 /crud/modify/crud/id 子路径。有关路径匹配算法的更多信息,请参阅本指南稍后 匹配多个路径:最长路径优先
4
您可以指定 java.security.Permission 类的自定义实现。自定义类必须精确定义一个构造器,接受权限名称和可选的操作,如 String 数组。在这种情况下,权限列表被添加到 SecurityIdentity 实例中,作为 新的 CustomPermission (" list ")

您还可以使用额外的构造器参数创建自定义 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
    }
}
Copy to Clipboard Toggle word wrap

1
自定义 权限 类必须正好有一个构造器。第一个参数始终被视为权限名称,且必须是 String 类型。Quarkus 可以选择性地将权限操作传给构造器。要发生这种情况,请将第二个参数声明为 String[]

如果允许 SecurityIdentity 执行必要的操作之一,则 LibraryPermission 类允许访问当前或父库,如 读取写入或 列表

以下示例演示了如何使用 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 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;
    }

}
Copy to Clipboard Toggle word wrap
1
正式参数 更新 被识别为第一个 Library 参数,并传递到 LibraryPermission 类。但是,每次调用 updateLibrary 方法时,必须实例化 LibraryPermission
2
在这里,第一个 参数是 migrate,因此 library 参数通过 PermissionsAllowed#params 明确标记为。权限构造器和注解的方法必须设置参数 ;否则,验证失败。

使用 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);
    }

}
Copy to Clipboard Toggle word wrap

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

    }

}
Copy to Clipboard Toggle word wrap
1
在构建原生可执行文件时,必须注册权限类进行反映,除非它至少用于一个 io.quarkus.security.PermissionsAllowed#name 参数。
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
Copy to Clipboard Toggle word wrap
1
授予权限 media-library,允许 读取list 操作。由于 MediaLibraryTvLibrary 类父项,因此也允许具有 admin 角色的用户修改 TvLibrary
提示

/libraryAttr 路径可以从 Keycloak 供应商 Dev UI 页面中测试,因为用户 aliceKeycloak 的 Dev Services 自动创建的用户具有 admin 角色。

目前为止提供的示例演示角色到权限映射。也可以以编程方式向 SecurityIdentity 实例添加权限。在以下示例中,SecurityIdentity 被自定义,以添加之前通过基于 HTTP 基于角色的访问控制相同的权限。

以编程方式将 LibraryPermission 添加到 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();
    }

}
Copy to Clipboard Toggle word wrap

1
创建的权限 media-library 可以执行 读取写入 和列出 操作。由于 MediaLibraryTvLibrary 类父项,因此也允许具有 admin 角色的用户修改 TvLibrary
2
您可以通过 io.quarkus.security.runtime.QuarkusSecurityIdentity.Builder#addPermissionChecker 添加权限检查程序。
Important

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

1.3. 参考

法律通告

Copyright © 2025 Red Hat, Inc.
The text of and illustrations in this document are licensed by Red Hat under a Creative Commons Attribution–Share Alike 3.0 Unported license ("CC-BY-SA"). An explanation of CC-BY-SA is available at http://creativecommons.org/licenses/by-sa/3.0/. In accordance with CC-BY-SA, if you distribute this document or an adaptation of it, you must provide the URL for the original version.
Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law.
Red Hat, Red Hat Enterprise Linux, the Shadowman logo, the Red Hat logo, JBoss, OpenShift, Fedora, the Infinity logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries.
Linux® is the registered trademark of Linus Torvalds in the United States and other countries.
Java® is a registered trademark of Oracle and/or its affiliates.
XFS® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries.
MySQL® is a registered trademark of MySQL AB in the United States, the European Union and other countries.
Node.js® is an official trademark of Joyent. Red Hat is not formally related to or endorsed by the official Joyent Node.js open source or commercial project.
The OpenStack® Word Mark and OpenStack logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community.
All other trademarks are the property of their respective owners.
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2026 Red Hat
返回顶部