如何通过Keycloak的AdminEvent获取已删除资源的详情?
解决Keycloak删除资源时AdminEvent无法获取详情的问题
嘿,这个问题我之前在做Keycloak管理员事件监听的时候也踩过坑,咱们一步步来拆解解决方案:
核心原因先搞懂
Keycloak的PostAdminEventListener是在操作完成后触发的,当执行删除用户/角色/组这类操作时,目标资源已经被移除,所以adminEvent.getRepresentation()自然会返回null。而要拿到删除前的资源详情,关键是要在删除操作执行前拦截并保存数据。
可行解决方案
1. 用PreAdminEventListener提前拦截并保存资源详情
这是最直接的方案:实现PreAdminEventListener接口,在删除操作真正执行前捕获资源的完整信息,提前存到缓存或数据库里,后续在PostAdminEventListener中就能通过资源ID取出这些数据。
示例代码:
// 预删除事件监听器,负责保存待删除资源的详情 public class PreDeleteResourceListener implements PreAdminEventListener { // 可以用分布式缓存(比如Redis)或Keycloak内置缓存,集群环境建议用分布式的 private final Cache<String, Object> resourceCache = CacheProvider.getCache(); @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { // 只处理删除操作 if (OperationType.DELETE.equals(event.getOperationType())) { Object resourceDetails = event.getResourceRepresentation(); if (resourceDetails != null) { // 从资源路径中提取唯一ID,比如"users/123e4567-e89b-12d3-a456-426614174000"取最后一段 String resourceId = extractResourceId(event.getResourcePath()); // 把详情存入缓存,设置一个合理的过期时间,避免内存浪费 resourceCache.put(resourceId, resourceDetails, Duration.ofMinutes(5)); } } } private String extractResourceId(String resourcePath) { String[] pathSegments = resourcePath.split("/"); return pathSegments[pathSegments.length - 1]; } } // 事后事件监听器,取出之前保存的删除资源详情 public class PostDeleteResourceListener implements PostAdminEventListener { private final Cache<String, Object> resourceCache = CacheProvider.getCache(); @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { if (OperationType.DELETE.equals(event.getOperationType())) { String resourceId = extractResourceId(event.getResourcePath()); Object deletedResource = resourceCache.get(resourceId); if (deletedResource != null) { // 根据资源类型做不同处理 if (ResourceType.USER.equals(event.getResourceType())) { UserRepresentation deletedUser = (UserRepresentation) deletedResource; // 在这里处理已删除用户的逻辑,比如记录审计日志 } else if (ResourceType.ROLE.equals(event.getResourceType())) { RoleRepresentation deletedRole = (RoleRepresentation) deletedResource; // 处理角色删除逻辑 } // 用完记得从缓存删除,避免冗余 resourceCache.remove(resourceId); } } } private String extractResourceId(String resourcePath) { String[] pathSegments = resourcePath.split("/"); return pathSegments[pathSegments.length - 1]; } }
2. 利用Keycloak Session在预删除时直接查询资源
如果不想依赖缓存,也可以在PreAdminEventListener中通过Keycloak的内置Session获取资源实体:
@Override public void onEvent(AdminEvent event, boolean includeRepresentation) { if (OperationType.DELETE.equals(event.getOperationType()) && ResourceType.USER.equals(event.getResourceType())) { String userId = extractResourceId(event.getResourcePath()); // 通过Keycloak Session获取UserProvider UserProvider userProvider = event.getKeycloakSession().getProvider(UserProvider.class); UserModel deletedUser = userProvider.getUserById(userId); if (deletedUser != null) { // 这里可以拿到用户的完整信息,比如用户名、邮箱等 String username = deletedUser.getUsername(); // 保存或处理这些数据 } } }
这种方式不需要额外缓存,但要注意:只能在Pre事件中使用,Post事件触发时资源已经被删除,Session里也查不到了。
注意事项
- 集群部署时,一定要用分布式缓存(比如Redis)来存储预删除的资源详情,避免不同节点之间数据不互通。
- 不同的
ResourceType对应不同的Representation类型,比如USER对应UserRepresentation,ROLE对应RoleRepresentation,需要做类型转换后再处理。 - 缓存的过期时间要设置合理,既保证Post事件能拿到数据,又不会占用过多内存。
内容的提问来源于stack exchange,提问作者LogronJ
相关产品推荐
相关产品推荐

