在Quarkus中实现RBAC

REST API 是任何现代软件应用程序的核心。保护对 REST API 的访问对于防止未经授权的操作和保护敏感数据至关重要。此外,公司必须遵守法规和标准才能成功运营。

本文介绍了如何在Quarkus Java框架中使用基于角色的访问控制(RBAC)来保护REST API。Quarkus 是一个开源的全栈 Java 框架,专为构建云原生容器化应用程序而设计。Quarkus Java 框架附带了对 RBAC 的原生支持,这将是本文的初始重点。此外,本文还将介绍如何构建自定义解决方案来保护 REST 端点。

概念
在这里插入图片描述
认证:身份验证是验证用户身份的过程,通常涉及使用用户名和密码。(但是,也可以采用其他方法,例如生物识别和双因素身份验证)。身份验证是安全的关键要素,对于保护系统和资源免受未经授权的访问至关重要。
授权:授权是验证用户是否具有访问特定资源或执行操作的必要权限的过程。通常,授权在身份验证之后进行。可以使用多种方法(例如基于角色的访问控制和基于属性的访问控制)来实现授权。
基于角色的访问控制:基于角色的访问控制 (RBAC) 是一种安全模型,它根据分配给用户的角色授予对资源的访问权限。在 RBAC 中,将用户分配给特定角色,并为每个角色授予执行其工作职能所需的权限。
网关:在传统的软件设置中,网关负责对客户端进行身份验证,并验证客户端是否具有访问资源所需的权限。网关身份验证在保护基于微服务的架构方面发挥着关键作用,因为它允许组织实施集中式身份验证。
基于令牌的身份验证:这是一种技术,网关在成功进行身份验证后向客户端提供访问令牌。然后,客户端在每个后续请求中向网关提供访问令牌。
智威汤逊:JSON Web 令牌 (JWT) 是一种广泛接受的标准,用于以 JSON 对象的形式在各方之间安全地传输信息。成功登录后,网关会生成一个 JWT 并将其发送回客户端。然后,客户端将 JWT 包含在对服务器的每个后续请求的标头中。JWT 可以包含所需的权限,这些权限可用于根据用户的授权级别允许或拒绝对 API 的访问。
应用实例
考虑一个简单的应用程序,其中包含用于创建和检索任务的 REST API。

应用程序有两个用户角色:

管理员:允许读取和写入。
成员:允许只读。
管理员和成员可以访问 GET API;但是,只有管理员有权使用 POST API。

@Path("/task")
public class TaskResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getTask() {
        return "Task Data";
    }

    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public String createTask() {
        return "Valid Task received";
    }
}

配置Quarkus安全模块
为了在 Quarkus 中处理和验证传入的 JWT,需要包含以下 JWT 安全模块。

对于基于 maven 的项目,请将以下内容添加到pom.xml

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-jwt-build</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-security-jwt</artifactId>
    <scope>test</scope>
</dependency>

对于基于 gradle 的项目,请添加以下内容:

implementation("io.quarkus:quarkus-smallrye-jwt")
implementation("io.quarkus:quarkus-smallrye-jwt-build")
testImplementation("io.quarkus:quarkus-test-security-jwt")

实施 RBAC
Quarkus提供内置的RBAC支持,以基于用户角色保护REST API。这可以通过几个步骤完成。

第 1 步
利用 Quarkus 内置的 RBAC 支持的第一步是用允许访问它们的角色来注释 API。要添加的注解是 ,这是一个 JSR 250 安全注解,它指示只有当用户属于指定角色时才能访问给定的端点。@RolesAllowed

@GET
@RolesAllowed({"Admin", "Member"})
@Produces(MediaType.TEXT_PLAIN)
public String getTask() {
	return "Task Data";
}

@POST
@RolesAllowed({"Admin"})
@Produces(MediaType.TEXT_PLAIN)
public String createTask() {
	return "Valid Task received";
}

步骤 2
下一步是配置颁发者 URL 和公钥。这使Quarkus能够验证JWT并确保它没有被篡改。这可以通过将以下属性添加到位于文件夹中的文件来完成。application.properties /resources

mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=https://myapp.com/issuer
quarkus.native.resources.includes=publicKey.pem

mp.jwt.verify.publickey.location- 此配置指定 Quarkus 的公钥位置,该公钥必须位于类路径中。Quarkus寻找的默认位置是文件夹。/resources
mp.jwt.verify.issuer- 此属性表示令牌的颁发者,该令牌的创建者使用其私钥对其进行签名。
quarkus.native.resources.includes- 此属性通知 Quarks 将公钥作为资源包含在原生可执行文件中。
步骤 3
最后一步是将公钥添加到应用程序。创建一个名为 的文件,将公钥保存在其中。将文件复制到位于目录中的文件夹。publicKey.pem/resources/src

测试
Quarkus为单元测试提供了强大的支持,以确保代码质量,特别是在RBAC方面。使用注解,可以定义用户角色,并可以生成 JWT 以从单元测试中调用 REST API。@TestSecurity

@Test
@TestSecurity(user = "testUser", roles = "Admin")
public void testTaskPostEndpoint() {
	given().log().all()
    	.body("{id: task1}")
        .when().post("/task")
	.then()
    	.statusCode(200)
        .body(is("Valid Task received"));
}

自定义 RBAC 实现
随着应用程序的增长并包含其他功能,内置的 RBAC 支持可能会变得不足。编写良好的应用程序允许用户创建具有与其关联的特定权限的自定义角色。请务必分离角色和权限,并避免在代码中对其进行硬编码。角色可以被视为权限的集合,并且每个 API 都可以使用访问它所需的权限进行标记。

为了分离角色和权限并为用户提供灵活性,让我们扩展我们的示例应用程序,以包含两个任务权限。

task:read:权限将允许用户读取任务
task:write:权限将允许用户创建或修改任务。
然后,我们可以将这些权限与两个角色相关联:“管理员”和“成员”

管理员:已分配读取和写入。[“task:read”, “task:write”]
成员:只会读。[“task:read”]
第 1 步
为了将每个 API 与权限相关联,我们需要一个自定义注释来简化其用法和应用。让我们创建一个名为 的新注解,它接受用户必须具有的字符串权限才能调用 API。@Permissions

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Permissions {
    String[] value();
}

步骤 2
可以将注释添加到任务 API 中,以指定访问它们所需的权限。如果用户具有 OR 权限,则可以访问 GET 任务 API,而 POST 任务 API 只有在用户具有权限时才能访问。@Permissionstask: readtask: writetask: write

@GET
@Permissions({"task:read", "task:write"})
@Produces(MediaType.TEXT_PLAIN)
public String getTask() {
	return "Task Data";
}

@POST
@Permissions("task:write")
@Produces(MediaType.TEXT_PLAIN)
public String createTask() {
	return "Valid Task received";
}

步骤 3
最后一步涉及添加一个筛选器,用于拦截 API 请求并验证包含的 JWT 是否具有调用 REST API 所需的权限。JWT 必须将用户 ID 作为声明的一部分包含在声明中,这在典型应用程序中就是这种情况,因为 JWT 令牌中包含某种形式的用户标识

反射 API 用于确定调用的方法及其关联的注释。在提供的代码中,映射和映射存储在 HashMaps 中。在实际场景中,将从数据库中检索并缓存此信息,以便更快地访问。user -> rolerole -> permissions

@Provider
public class PermissionFilter implements ContainerRequestFilter {

    @Context
    ResourceInfo resourceInfo;

    @Inject
    JsonWebToken jwt;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        Method method = resourceInfo.getResourceMethod();
        Permissions methodPermAnnotation = method.getAnnotation(Permissions.class);

        if(methodPermAnnotation != null && checkAccess(methodPermAnnotation)) {
            System.out.println("Verified permissions");
        } else {
            requestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
        }
    }

    /**
     * Verify if JWT permissions match the API permissions
     */
    private boolean checkAccess(Permissions perm) {
        boolean verified = false;
        if(perm == null) {
			//If no permission annotation verification failed
            verified = false;
        } else if(jwt.getClaim("userId") == null) {
			// Don’t support Anonymous users
            verified = false;
        }
        else {
            String userId = jwt.getClaim("userId");
            String role = getRolesForUser(userId);
            String[] userPermissions = getPermissionForRole(role);            
            if(Arrays.asList(userPermissions).stream()
               .anyMatch(userPerm -> Arrays.asList(perm.value()).contains(userPerm))) {
                 verified = true;
            }
        }
        return verified;
    }

    // role -> permission mapping
    private String[] getPermissionForRole(String role) {
        Map<String, String[]> rolePermissionMap = new HashMap<>();
        rolePermissionMap.put("Admin", new String[] {"task:write", "task:read"});
        rolePermissionMap.put("Member", new String[] {"task:read"});
        return rolePermissionMap.get(role);
    }

    // userId -> role mapping
    private String getRolesForUser(String userId) {
        Map<String, String> userMap = new HashMap<>();
        userMap.put("1234", "Admin");
        userMap.put("6789", "Member");
        return userMap.get(userId);
    }
    
}

测试
与测试内置 RBAC 的方式类似,注释可用于创建用于测试目的的 JWT。此外,Quarkus库还提供了注释,允许向JWT添加额外的声明,包括userId声明。@TestSecurity @JwtSecurity

@Test
@TestSecurity(user = "testUser", roles = "Admin")
@JwtSecurity(claims = {
	@Claim(key = "userId", value = "1234")
})
public void testTaskPosttEndpoint() {
	given().log().all()
    	.body("{id: task1}")
        .when().post("/task")
    .then()
        .statusCode(200)
        .body(is("Task edited"));
}

@Test
@TestSecurity(user = "testUser", roles = "Admin")
@JwtSecurity(claims = {
	@Claim(key = "userId", value = "6789")
})
public void testTaskPostMember() {
	given().log().all()
    	.body("{id: task1}")
		.when().post("/task")
	.then()
    	.statusCode(403);
}

结论
随着网络攻击的不断增加,保护 REST API 变得越来越重要。潜在的安全漏洞可能会给公司带来巨大的经济损失和声誉损害。虽然 Quarkus 是一个多功能的 Java 框架,它提供了内置的 RBAC 支持来保护 REST API,但在某些情况下,它的原生支持可能不足,尤其是对于细粒度的访问控制。上面的文章涵盖了Quarkus中内置RBAC支持的实现,以及Quarkus中基于角色的自定义访问控制解决方案的开发和测试。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
RBAC(Role-Based Access Control)是一种常见的权限控制模型,它将用户赋予的权限与角色相关联,而不是直接赋予用户权限。这种模型的优点是易于管理和维护。 在实现RBAC权限时,需要考虑以下几个方面: 1. 角色设计:首先需要定义系统的角色,通常可以根据业务需求来定义,例如管理员、普通用户、VIP用户等。 2. 权限设计:其次需要定义每个角色所具有的权限,例如管理员可以访问所有资源,而普通用户只能访问部分资源。 3. 用户与角色关联:用户需要被赋予一个或多个角色,以便系统能够识别其权限。 4. 角色与权限关联:每个角色需要被赋予一组权限,以便系统能够根据角色确定用户的权限。 5. 权限验证:在用户访问资源时,需要根据其所具有的角色和权限进行验证,以确保其有权限访问该资源。 在具体实现RBAC权限时,可以采用数据库表设计的方式来存储角色、用户、权限等信息。通常会有以下几个表: 1. Role表:存储角色信息,包括角色ID、角色名称、角色描述等。 2. User表:存储用户信息,包括用户ID、用户名、密码等。 3. UserRole表:存储用户与角色的关联信息,包括用户ID、角色ID等。 4. Permission表:存储权限信息,包括权限ID、权限名称、权限描述等。 5. RolePermission表:存储角色与权限的关联信息,包括角色ID、权限ID等。 在实现RBAC权限时,需要注意以下几点: 1. 需要对用户输入的数据进行验证和过滤,以避免SQL注入等安全问题。 2. 需要确保角色、用户、权限等信息的一致性和完整性,例如添加用户时需要保证其所属角色存在。 3. 需要对数据进行备份和恢复,以避免数据丢失或损坏。 4. 需要确保系统的性能和可靠性,例如采用索引等技术来提高查询效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小徐博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值