1.2. 使用注解进行授权


Quarkus 包括内置的安全性,以根据通用安全注释 @RolesAllowed@DenyAll@PermitAll on REST 端点和 CDI Bean 来允许 基于角色的访问控制(RBAC)

注意

在检查标准安全注解前,执行对 quarkus.http.auth. 配置的授权检查。因此,@PermitAll 仅允许访问尚未受 HTTP 权限限制的路径。@PermitAll 无法覆盖 HTTP 级别的安全配置,只有其他标准安全注释(如 @RolesAllowed )实施的限制。

Expand
表 1.2. Quarkus 注解类型概述
注解类型描述

@DenyAll

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

@PermitAll

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

@PermitAll 可让任何人都有,即使没有身份验证。

@RolesAllowed

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

@authenticated

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

@PermissionsAllowed

指定允许调用指定方法的权限列表。

@AuthorizationPolicy

指定 named io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy,它应授权访问指定的 Jakarta REST 端点。名为 HttpSecurityPolicy 可以用来进行常规授权检查,如自定义 named HttpSecurityPolicy 绑定到 Jakarta REST 端点的 示例所示。

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

SubjectExposingResource 示例

import java.security.Principal;

import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;

@Path("subject")
public class SubjectExposingResource {

    @GET
    @Path("secured")
    @RolesAllowed("Tester") 
1

    public String getSubjectSecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); 
2

        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("authenticated")
    @Authenticated 
3

    public String getSubjectAuthenticated(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("unsecured")
    @PermitAll 
4

    public String getSubjectUnsecured(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal(); 
5

        String name = user != null ? user.getName() : "anonymous";
        return name;
    }

    @GET
    @Path("denied")
    @DenyAll 
6

    public String getSubjectDenied(@Context SecurityContext sec) {
        Principal user = sec.getUserPrincipal();
        String name = user != null ? user.getName() : "anonymous";
        return name;
    }
}
Copy to Clipboard Toggle word wrap

1
/subject/secured 端点需要通过使用 @RolesAllowed ("Tester") 注释而具有授权的"Tester"角色的经过身份验证的用户。
2
端点从 Jakarta REST SecurityContext 获取用户主体。这会为安全端点返回 非null
3
/subject/authenticated 端点可通过指定 @Authenticated 注释来允许任何经过身份验证的用户。
4
/subject/unsecured 端点通过指定 @PermitAll 注释来允许未经身份验证的访问。
5
如果调用者被验证,则获取用户主体的调用会返回 null
6
/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.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. 端点安全注解和 Jakarta REST 继承

Quarkus 支持放置在端点实现或其类上,如下例所示:

@Path("hello")
public interface HelloInterface {

    @GET
    String hello();

}

@DenyAll 
1

public class HelloInterfaceImpl implements HelloInterface {

    @RolesAllowed("admin") 
2

    @Override
    public String hello() {
        return "Hello";
    }
}
Copy to Clipboard Toggle word wrap
1
类级别的安全注解必须放在声明端点实施的类中。
2
方法级安全注解必须放在端点实现中。
重要

声明为默认接口方法的 RESTEasy 子资源 locators 无法被标准安全注解保护。必须在接口实现器上实现并保护安全的子资源 locators,如下例所示:

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

1.2.2. 权限注解

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

使用 @PermissionsAllowed 注释保护的端点示例

package org.acme.crud;

import io.quarkus.arc.Arc;
import io.vertx.ext.web.RoutingContext;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

import io.quarkus.security.PermissionsAllowed;

import java.security.BasicPermission;
import java.security.Permission;
import java.util.Collection;
import java.util.Collections;

@Path("/crud")
public class CRUDResource {

    @PermissionsAllowed("create") 
1

    @PermissionsAllowed("update")
    @POST
    @Path("/modify/repeated")
    public String createOrUpdate() {
        return "modified";
    }

    @PermissionsAllowed(value = {"create", "update"}, inclusive=true) 
2

    @POST
    @Path("/modify/inclusive")
    public String createOrUpdate(Long id) {
        return id + " modified";
    }

    @PermissionsAllowed({"see:detail", "see:all", "read"}) 
3

    @GET
    @Path("/id/{id}")
    public String getItem(String id) {
        return "item-detail-" + id;
    }

    @PermissionsAllowed(value = "list", permission = CustomPermission.class) 
4

    @Path("/list")
    @GET
    public Collection<String> list(@QueryParam("query-options") String queryOptions) {
        // your business logic comes here
        return Collections.emptySet();
    }

    public static class CustomPermission extends BasicPermission {

        public CustomPermission(String name) {
            super(name);
        }

        @Override
        public boolean implies(Permission permission) {
            var event = Arc.container().instance(RoutingContext.class).get(); 
5

            var publicContent = "public-content".equals(event.request().params().get("query-options"));
            var hasPermission = getName().equals(permission.getName());
            return hasPermission && publicContent;
        }
    }
}
Copy to Clipboard Toggle word wrap

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

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

注意

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

为启用了角色的 SecurityIdentity 实例添加权限的最简单方法是将角色映射到权限。使用 Authorization using configurationCRUDResource 端点所需的 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
将权限 seeall 添加到 用户角色的 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 library) {
        library.description = newDesc;
        return library;
    }

    @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class) 
2

    @PermissionsAllowed(value = {"tv:read", "tv:list"}, permission = LibraryPermission.class)
    public Library migrateLibrary(Library migrate, Library library) {
        // migrate libraries
        return library;
    }

}
Copy to Clipboard Toggle word wrap
1
正式参数 被识别为与同名 库Permission 构造器参数匹配的参数。因此,Quarkus 会将 library 参数传递给 LibraryPermission 类构造器。但是,每次调用 updateLibrary 方法时,都必须实例化 LibraryPermission
2
此处,第二个 Library 参数与名称 匹配,而 migrate 参数则在 LibraryPermission 权限实例化期间会被忽略。

使用 LibraryPermission保护的资源示例

package org.acme.library;

import io.quarkus.security.PermissionsAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.acme.library.LibraryPermission.Library;

@Path("/library")
public class LibraryResource {

    @Inject
    LibraryService libraryService;

    @PermissionsAllowed(value = "tv:write", permission = LibraryPermission.class)
    @PUT
    @Path("/id/{id}")
    public Library updateLibrary(@PathParam("id") Integer id, Library library) {
        ...
    }

    @PUT
    @Path("/service-way/id/{id}")
    public Library updateLibrarySvc(@PathParam("id") Integer id, Library library) {
        String newDescription = "new description " + id;
        return libraryService.updateLibrary(newDescription, library);
    }

}
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 参数中使用。有关 @RegisterForReflection 注释的更多详细信息,请参阅 原生应用程序提示 页面。
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,允许 读取写入 和列出 操作。因为 MediaLibraryTvLibrary 类父级,因此还允许具有 admin 角色的用户修改 TvLibrary
提示

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

目前提供的示例演示有角色到权限映射。也可以以编程方式向 SecurityIdentity 实例添加权限。在以下示例中,自定义SecurityIdentity 来添加之前通过 HTTP 角色策略授予的相同权限。

以编程方式将 LibraryPermission 添加到 SecurityIdentity的示例

import java.security.Permission;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityIdentityAugmentor;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class PermissionsIdentityAugmentor implements SecurityIdentityAugmentor {

    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
        if (isNotAdmin(identity)) {
            return Uni.createFrom().item(identity);
        }
        return Uni.createFrom().item(build(identity));
    }

    private boolean isNotAdmin(SecurityIdentity identity) {
        return identity.isAnonymous() || !"admin".equals(identity.getPrincipal().getName());
    }

    SecurityIdentity build(SecurityIdentity identity) {
        return QuarkusSecurityIdentity.builder(identity)
                .addPermission(new MediaLibraryPermission("media-library", new String[] { "read", "write", "list"}); 
1

                .build();
    }

}
Copy to Clipboard Toggle word wrap

1
添加可执行 读取写入 和列出 操作的 media-library 权限。因为 MediaLibraryTvLibrary 类父级,因此还允许具有 admin 角色的用户修改 TvLibrary
Important

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

1.2.2.1. 创建权限检查器

默认情况下,SecurityIdentity 必须配置有权限,可用于检查此身份是否通过 @PermissionAllowed 授权限制。或者,您可以使用 @PermissionChecker 注释将任何 CDI bean 方法标记为权限检查程序。@PermissionChecker 注释值应与 @PermissionsAllowed 注释值声明所需的权限匹配。例如,可以创建权限检查程序,如下所示:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestPath;

@Path("/project/{projectName}")
public class ProjectResource {

    @PermissionsAllowed("rename-project") 
1

    @POST
    public void renameProject(@RestPath String projectName, @RestForm String newName) {
        Project project = Project.findByName(projectName);
        project.name = newName;
    }

    @PermissionChecker("rename-project") 
2

    boolean canRenameProject(SecurityIdentity identity, String projectName) { 
3
 
4

        var principalName = identity.getPrincipal().getName();
        var user = User.getUserByName(principalName);
        return userOwnsProject(projectName, user);
    }
}
Copy to Clipboard Toggle word wrap
1
访问 ProjectResource-<renameProject 所需的权限是 rename-project 权限。
2
ProjectResource#canRenameProject 方法授权访问 ProjectResource:renameProject 端点。
3
SecurityIdentity 实例可以注入到任何权限检查程序方法中。
4
在本例中,同一资源上声明 rename-project 权限检查程序。但是,在下一个示例中,无法声明此权限检查程序。
注意

权限检查器方法可以在普通范围的 CDI Bean 或 @Singleton bean 上声明。目前不支持 @Dependent CDI bean 范围。

上面的权限检查程序需要 SecurityIdentity 实例授权 renameProject 端点。您可以在资源上直接声明 rename-project 权限检查程序,您可以在任何 CDI bean 上声明它,如下例所示:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionChecker;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped 
1

public class ProjectPermissionChecker {

    @PermissionChecker("rename-project")
    boolean canRenameProject(String projectName, SecurityIdentity identity) {   
2

        var principalName = identity.getPrincipal().getName();
        var user = User.getUserByName(principalName);
        return userOwnsProject(projectName, user);
    }

}
Copy to Clipboard Toggle word wrap
1
具有权限检查器的 CDI bean 必须是普通范围的 bean 或 @Singleton bean。
2
权限检查器方法必须返回 布尔值Uni<Boolean>。不支持私有检查程序方法。
提示

默认情况下,权限检查在事件循环上运行。如果要在 worker 线程上运行检查,使用 io.smallrye.common.annotation.Blocking 注解注解权限检查程序方法。

@PermissionsAllowed 值和 @PermissionChecker 值之间的匹配基于字符串 equality,如下例所示:

package org.acme.security;

import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class FileService {

    @PermissionsAllowed({ "delete:all", "delete:dir" }) 
1

    void deleteDirectory(Path directoryPath) {
        // delete directory
    }

    @PermissionsAllowed(value = { "delete:service", "delete:file" }, inclusive = true) 
2

    void deleteServiceFile(Path serviceFilePath) {
        // delete service file
    }

    @PermissionChecker("delete:all")
    boolean canDeleteAllDirectories(SecurityIdentity identity) {
        String filePermissions = identity.getAttribute("user-group-file-permissions");
        return filePermissions != null && filePermissions.contains("w");
    }

    @PermissionChecker("delete:service")
    boolean canDeleteService(SecurityIdentity identity) {
        return identity.hasRole("admin");
    }

    @PermissionChecker("delete:file")
    boolean canDeleteFile(Path serviceFilePath) {
        return serviceFilePath != null && !serviceFilePath.endsWith("critical");
    }
}
Copy to Clipboard Toggle word wrap
1
权限检查器方法 canDeleteAllDirectories 授予对 deleteDirectory 的访问权限,因为 delete:all 值是相等的。
2
必须正好有两个权限检查程序方法,一个用于 delete:service 权限,另一个用于 delete:file 权限。

1.2.2.2. 创建权限 meta-annotations

@PermissionsAllowed 也可以用于 meta-annotations。例如,可以创建一个新的 @CanWrite 安全注解,如下所示:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import io.quarkus.security.PermissionsAllowed;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@PermissionsAllowed(value = "write", permission = CustomPermission.class) 
1

public @interface CanWrite {

}
Copy to Clipboard Toggle word wrap
1
使用 @CanWrite 注释标注的任何方法或类都通过 @PermissionsAllowed 注释实例进行保护。

1.2.2.3. 将 @BeanParam 参数传递给自定义权限

Quarkus 可以将安全方法参数的字段映射到自定义权限构造器参数。您可以使用此功能将 jakarta.ws.rs.BeanParam 参数传递给您的自定义权限。让我们考虑以下 Jakarta REST 资源:

package org.acme.security.rest.resource;

import io.quarkus.security.PermissionsAllowed;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/hello")
public class HelloResource {

    @PermissionsAllowed(value = "say-hello", params = "beanParam.securityContext.userPrincipal.name") 
1

    @GET
    public String sayHello(@BeanParam SimpleBeanParam beanParam) {
        return "Hello from " + beanParam.uriInfo.getPath();
    }

}
Copy to Clipboard Toggle word wrap
1
params 注解属性指定用户主体名称应传递给 BeanParamPermissionChecker#canSayHello 方法。其他 BeanParamPermissionChecker#canSayHello 方法参数(如 customAuthorizationHeader查询 )会自动匹配。Quarkus 在 beanParam 字段及其公共访问器之间标识 BeanParamPermissionChecker#canSayHello 方法参数。为避免模糊解析,自动检测仅适用于 beanParam 字段。因此,我们必须明确指定用户主体名称的路径。

其中声明了 SimpleBeanParam 类,如下例所示:

package org.acme.security.rest.dto;

import java.util.List;

import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;

public class SimpleBeanParam {

    @HeaderParam("CustomAuthorization")
    private String customAuthorizationHeader;

    @Context
    SecurityContext securityContext;

    @Context
    public UriInfo uriInfo;

    @QueryParam("query")
    public String query;    
1


    public SecurityContext getSecurityContext() { 
2

        return securityContext;
    }

    public String customAuthorizationHeader() { 
3

        return customAuthorizationHeader;
    }
}
Copy to Clipboard Toggle word wrap
1
Quarkus Security 只能将公共字段传递给自定义权限构造器。
2
如果可用,quarkus Security 会自动使用公共 getter 方法。
3
customAuthorizationHeader 字段不是公共的,因此 Quarkus 使用 customAuthorizationHeader accessor 访问此字段。这对 Java 记录特别有用,其中生成的访问器不是前缀。

下面是一个 @PermissionChecker 方法的示例,它根据用户主体、自定义标头和查询参数检查 say-hello 权限:

package org.acme.security.permission;

import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.security.PermissionChecker;

@ApplicationScoped
public class BeanParamPermissionChecker {

    @PermissionChecker("say-hello")
    boolean canSayHello(String customAuthorizationHeader, String name, String query) {
        boolean queryParamAllowedForPermissionName = checkQueryParams(query);
        boolean usernameWhitelisted = isUserNameWhitelisted(name);
        boolean customAuthorizationMatches = checkCustomAuthorization(customAuthorizationHeader);
        return queryParamAllowedForPermissionName && usernameWhitelisted && customAuthorizationMatches;
    }

    ...
}
Copy to Clipboard Toggle word wrap
注意

您可以将 @BeanParam 直接传递给 @PermissionChecker 方法,并以编程方式访问其字段。当您有多个不同的结构化 @BeanParam 类时,使用 @PermissionsAllowed Serialparams 属性引用 @BeanParam 字段的功能很有用。

返回顶部
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

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

让开源更具包容性

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

關於紅帽

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

Theme

© 2025 Red Hat