1. 概述
本教程将探讨基于角色的访问控制(RBAC)机制,以及如何使用Quarkus框架实现该功能。
RBAC是构建复杂安全系统的经典方案。Quarkus作为现代化的云原生全栈Java框架,原生支持RBAC功能。需要说明的是,角色在实际应用中有多种实现方式:
- 在企业系统中,角色通常是权限的聚合,用于标识用户可执行的操作组
- 在Jakarta规范中,角色本质是允许执行资源操作的标签(等同于权限)
本教程将采用资源权限分配的方式控制访问,角色则作为权限的集合容器。
2. RBAC核心原理
基于角色的访问控制是一种安全模型,通过预定义的权限授予用户应用访问权。系统管理员在访问尝试时:
- ✅ 为特定资源分配权限
- ✅ 验证用户权限
- ✅ 通过角色分组管理权限
为演示Quarkus中的RBAC实现,我们将结合以下技术:
- JWT:实现身份验证和授权的自包含方案
- JPA:处理领域逻辑与数据库交互
- Quarkus Security:整合所有安全组件
3. JWT机制解析
JSON Web Tokens (JWT)是用户与服务器间安全传输信息的紧凑型URL安全JSON对象。该令牌具有数字签名,常用于Web应用的身份验证和安全数据交换:
核心流程:
- 客户端提供凭证请求令牌
- 授权服务器返回签名令牌
- 客户端携带JWT访问资源
- 资源服务器验证令牌及所需权限
接下来我们将探讨如何在Quarkus中整合RBAC与JWT。
4. 数据模型设计
为简化示例,我们设计基础RBAC数据模型,包含以下表结构:
该模型支持:
- 用户管理
- 角色分配
- 权限组合
使用JPA映射领域对象:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true)
private String username;
@Column
private String password;
@Column(unique = true)
private String email;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_name"))
private Set<Role> roles = new HashSet<>();
// Getter and Setters
}
用户表存储登录凭证及角色关联关系:
@Entity
@Table(name = "roles")
public class Role {
@Id
private String name;
@Roles
@Convert(converter = PermissionConverter.class)
private Set<Permission> permissions = new HashSet<>();
// Getters and Setters
}
⚠️ 权限以逗号分隔值存储在单列中,通过PermissionConverter
实现转换。
5. JWT与Quarkus集成
启用JWT令牌和登录功能需添加以下依赖:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt-build</artifactId>
<version>3.9.4</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-jwt</artifactId>
<version>3.9.4</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security</artifactId>
<scope>test</scope>
<version>3.9.4</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security-jwt</artifactId>
<scope>test</scope>
<version>3.9.4</version>
</dependency>
核心依赖说明:
quarkus-smallrye-jwt-build
:令牌生成quarkus-smallrye-jwt
:权限验证quarkus-test-security(-jwt)
:测试支持
配置RSA密钥实现令牌签名:
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=my-issuer
smallrye.jwt.sign.key.location=privateKey.pem
Quarkus默认在/resources
或指定绝对路径查找密钥文件,用于:
- 签名claims
- 验证令牌有效性
6. 凭证处理
创建JWT令牌并设置权限需先验证用户凭证:
@Path("/secured")
public class SecureResourceController {
// other methods...
@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermitAll
public Response login(@Valid final LoginDto loginDto) {
if (userService.checkUserCredentials(loginDto.username(), loginDto.password())) {
User user = userService.findByUsername(loginDto.username());
String token = userService.generateJwtToken(user);
return Response.ok().entity(new TokenResponse("Bearer " + token,"3600")).build();
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity(new Message("Invalid credentials")).build();
}
}
}
关键点说明:
@PermitAll
:声明公开接口(无需认证)- 验证成功返回Bearer令牌
- 验证失败返回401状态码
核心方法generateJwtToken
实现令牌生成:
public String generateJwtToken(final User user) {
Set<String> permissions = user.getRoles()
.stream()
.flatMap(role -> role.getPermissions().stream())
.map(Permission::name)
.collect(Collectors.toSet());
return Jwt.issuer(issuer)
.upn(user.getUsername())
.groups(permissions)
.expiresIn(3600)
.claim(Claims.email_verified.name(), user.getEmail())
.sign();
}
执行流程:
- 收集用户所有角色的权限
- 设置令牌核心属性:
- 签发方(issuer)
- 用户标识(upn)
- 权限组(groups)
- 过期时间(3600秒)
- 自定义声明(email_verified)
- 签名生成令牌
客户端后续请求只需在Authorization
头携带Bearer令牌即可完成认证。
7. 权限控制
Jakarta规范使用@RolesAllowed
注解分配权限(虽命名为roles但实际作为权限使用):
@Path("/secured")
public class SecureResourceController {
private final UserService userService;
private final SecurityIdentity securityIdentity;
// constructor
@GET
@Path("/resource")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"VIEW_ADMIN_DETAILS"})
public String get() {
return "Hello world, here are some details about the admin!";
}
@GET
@Path("/resource/user")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({"VIEW_USER_DETAILS"})
public Message getUser() {
return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
}
//...
}
权限控制要点:
/secured/resource
:需要VIEW_ADMIN_DETAILS
权限/secured/resource/user
:需要VIEW_USER_DETAILS
权限- ✅ 支持多权限列表(满足任一即可)
- ✅
SecurityIdentity
提供当前用户信息
8. 测试方案
Quarkus提供强大测试工具,简化JWT测试场景:
@QuarkusTest
class SecureResourceControllerTest {
@Test
@TestSecurity(user = "user", roles = "VIEW_USER_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@example.com")
})
void givenSecureAdminApi_whenUserTriesToAccessAdminApi_thenShouldNotAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/secured/resource")
.then()
.statusCode(403);
}
@Test
@TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "admin@example.com")
})
void givenSecureAdminApi_whenAdminTriesAccessAdminApi_thenShouldAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/secured/resource")
.then()
.statusCode(200)
.body(equalTo("Hello world, here are some details about the admin!"));
}
//...
}
测试注解说明:
@TestSecurity
:定义安全上下文(用户/角色)@JwtSecurity
:配置令牌声明(claims)- ✅ 支持多场景测试(权限不足/权限充足)
9. Quarkus安全扩展
Quarkus安全模块可与RBAC深度集成。虽然其原生权限系统不直接使用角色概念,但可通过配置建立映射关系:
quarkus.http.auth.policy.role-policy1.permissions.VIEW_ADMIN_DETAILS=VIEW_ADMIN_DETAILS
quarkus.http.auth.policy.role-policy1.permissions.VIEW_USER_DETAILS=VIEW_USER_DETAILS
quarkus.http.auth.policy.role-policy1.permissions.SEND_MESSAGE=SEND_MESSAGE
quarkus.http.auth.policy.role-policy1.permissions.CREATE_USER=CREATE_USER
quarkus.http.auth.policy.role-policy1.permissions.OPERATOR=OPERATOR
quarkus.http.auth.permission.roles1.paths=/permission-based/*
quarkus.http.auth.permission.roles1.policy=role-policy1
配置解析:
role-policy
:定义角色-权限映射- 格式:
quarkus.http.auth.policy.{策略名}.permissions.{角色名}={权限列表}
- 后两行:指定策略应用路径
接口实现需改用@PermissionsAllowed
注解:
@Path("/permission-based")
public class PermissionBasedController {
private final SecurityIdentity securityIdentity;
public PermissionBasedController(SecurityIdentity securityIdentity) {
this.securityIdentity = securityIdentity;
}
@GET
@Path("/resource/version")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@PermissionsAllowed("VIEW_ADMIN_DETAILS")
public String get() {
return "2.0.0";
}
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("/resource/message")
@PermissionsAllowed(value = {"SEND_MESSAGE", "OPERATOR"}, inclusive = true)
public Message message() {
return new Message("Hello "+securityIdentity.getPrincipal().getName()+"!");
}
}
关键特性:
inclusive=true
:要求同时满足所有权限(AND逻辑)- 默认
inclusive=false
:满足任一权限即可(OR逻辑)
测试用例验证复合权限场景:
@QuarkusTest
class PermissionBasedControllerTest {
@Test
@TestSecurity(user = "admin", roles = "VIEW_ADMIN_DETAILS")
@JwtSecurity(claims = {
@Claim(key = "email", value = "admin@example.com")
})
void givenSecureVersionApi_whenUserIsAuthenticated_thenShouldReturnVersion() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/version")
.then()
.statusCode(200)
.body(equalTo("2.0.0"));
}
@Test
@TestSecurity(user = "user", roles = "SEND_MESSAGE")
@JwtSecurity(claims = {
@Claim(key = "email", value = "user@example.com")
})
void givenSecureMessageApi_whenUserOnlyHasOnePermission_thenShouldNotAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/message")
.then()
.statusCode(403);
}
@Test
@TestSecurity(user = "new-operator", roles = {"SEND_MESSAGE", "OPERATOR"})
@JwtSecurity(claims = {
@Claim(key = "email", value = "operator@example.com")
})
void givenSecureMessageApi_whenUserOnlyHasBothPermissions_thenShouldAllowRequest() {
given()
.contentType(ContentType.JSON)
.get("/permission-based/resource/message")
.then()
.statusCode(200)
.body("message", equalTo("Hello new-operator!"));
}
}
测试覆盖:
- 单一权限访问(成功)
- 部分权限访问(失败)
- 完整权限访问(成功)
10. 总结
本文深入探讨了RBAC系统在Quarkus中的实现方案,重点对比了:
- 角色与权限的概念差异
- Jakarta规范与Quarkus安全模块的实现区别
- 不同场景下的测试策略
核心要点:
- ✅ JWT提供轻量级认证方案
- ✅ JPA简化权限数据管理
- ✅ 注解式权限控制提升开发效率
- ✅ 配置化权限映射增强灵活性
完整代码示例请参考GitHub仓库。