Quarkus中自定义ParamConverterProvider参数注入失效问题求助
问题分析与解决方案
核心问题原因
- ParamConverterProvider 适用场景错误:JAX-RS 的
ParamConverterProvider仅负责转换已从请求中获取的参数值(比如@QueryParam/@PathParam的字符串转目标类型),而非主动注入请求中不存在的参数。你的自定义注解@KeycloakRole未绑定任何标准参数注解,JAX-RS 运行时不会触发该转换器。 - Token 角色解析逻辑错误:Keycloak AccessToken 的
resource_access字段是JsonObject(键为客户端ID,值为包含角色的对象),而非你代码中假设的JsonArray,这会导致角色提取失败。 - SecurityContext 类型转换错误:直接将
SecurityContext强转为KeycloakSecurityContext是错误的,正确方式是从SecurityContext.getUserPrincipal()获取实例。
修正方案
1. 修改自定义注解,添加 CDI Qualifier 标识
CDI 需要通过 Qualifier 区分不同的注入类型,给@KeycloakRole添加@Qualifier注解:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) @Qualifier public @interface KeycloakRole { String realm(); String role(); }
2. 实现 CDI 生产者,负责角色注入与校验
用 CDI 生产者替代 ParamConverter,主动提取并校验用户角色:
@RequestScoped public class KeycloakRoleProducer { @Context SecurityContext securityContext; @Produces @KeycloakRole(realm = "", role = "") public Set<String> produceKeycloakRole(InjectionPoint injectionPoint) { // 获取方法参数上的注解配置 KeycloakRole annotation = injectionPoint.getAnnotated().getAnnotation(KeycloakRole.class); String requiredRole = annotation.role(); Set<String> userRoles = extractUserRoles(); // 校验用户是否拥有要求的角色 if (!userRoles.contains(requiredRole)) { throw new WebApplicationException("User does not have required role", Response.Status.FORBIDDEN); } return userRoles; } private Set<String> extractUserRoles() { // 从SecurityContext获取Keycloak上下文 KeycloakSecurityContext keycloakCtx = (KeycloakSecurityContext) securityContext.getUserPrincipal(); if (keycloakCtx == null) { throw new WebApplicationException("Unauthorized", Response.Status.UNAUTHORIZED); } AccessToken token = keycloakCtx.getToken(); if (token == null) { throw new WebApplicationException("Failed to access user token", Response.Status.UNAUTHORIZED); } // 正确解析resource_access结构 Map<String, Object> resourceAccess = token.getResourceAccess(); if (resourceAccess == null) { return Collections.emptySet(); } // 替换为你的实际客户端ID Object clientAccess = resourceAccess.get("profiler-core"); if (!(clientAccess instanceof JsonObject)) { return Collections.emptySet(); } JsonArray rolesArray = ((JsonObject) clientAccess).getJsonArray("roles"); if (rolesArray == null) { return Collections.emptySet(); } Set<String> roles = new HashSet<>(); for (int i = 0; i < rolesArray.size(); i++) { roles.add(rolesArray.getString(i)); } return roles; } }
3. 简化端点方法,直接注入角色
修改后的端点无需额外配置,CDI 会自动处理注入与校验:
@Path("/api/profile") @Authenticated @ApplicationScoped public class AdminResource { @GET @Path("/custom-role-ep") public Collection<String> customEp(@KeycloakRole(realm = "profiler", role = "view-all") Set<String> userRoles) { LOG.info("User roles: " + userRoles); return userRoles; } }
内容的提问来源于stack exchange,提问作者Ayume
相关产品推荐
相关产品推荐

