未集成Keycloak Spring Security时,如何从UserModel获取用户信息加入自定义日志?
解决方案:未集成Spring Security时将Keycloak用户信息注入自定义日志机制
以下是几种可行的实现方式,根据你的项目场景选择:
方案一:ThreadLocal + Keycloak事件监听器传递用户信息
这种方式适合已通过Keycloak事件监听器获取UserModel的场景,利用ThreadLocal在请求线程中传递用户信息,再通过拦截器注入到request属性或直接供日志工具使用。
- 编写ThreadLocal工具类,用于存储和管理当前线程的用户上下文:
public class UserContextHolder { private static final ThreadLocal<UserInfo> USER_CONTEXT = new ThreadLocal<>(); public static void setUserInfo(String userId, String userName) { USER_CONTEXT.set(new UserInfo(userId, userName)); } public static UserInfo getUserInfo() { return USER_CONTEXT.get(); } public static void clear() { USER_CONTEXT.remove(); } public static class UserInfo { private final String userId; private final String userName; public UserInfo(String userId, String userName) { this.userId = userId; this.userName = userName; } public String getUserId() { return userId; } public String getUserName() { return userName; } } }
- 在Keycloak事件监听器中写入用户信息到ThreadLocal:
public class CustomKeycloakEventListener implements EventListenerProvider { @Override public void onEvent(Event event) { // 仅处理用户登录成功事件 if (EventType.LOGIN.equals(event.getType())) { KeycloakSession session = ...; // 从监听器上下文获取当前KeycloakSession UserModel user = session.users().getUserById(event.getUserId(), session.getContext().getRealm()); UserContextHolder.setUserInfo(user.getId(), user.getUsername()); } } @Override public void onEvent(AdminEvent event, boolean includeRepresentation) { // 按需处理管理端事件,无需可留空 } @Override public void close() { // 资源清理操作 } }
- 自定义请求拦截器,将用户信息注入request属性:
public class LoggingInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { UserContextHolder.UserInfo userInfo = UserContextHolder.getUserInfo(); if (userInfo != null) { request.setAttribute("userId", userInfo.getUserId()); request.setAttribute("userName", userInfo.getUserName()); } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清理ThreadLocal,避免内存泄漏 UserContextHolder.clear(); } }
- 注册拦截器到Spring MVC配置:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoggingInterceptor()); } }
方案二:直接在日志工具类中读取ThreadLocal信息
如果不需要将用户信息放到request属性中,可直接在日志工具类里获取ThreadLocal中的用户信息,拼接成带用户标识的日志:
public class CustomLogger { private static final Logger logger = LoggerFactory.getLogger(CustomLogger.class); public static void info(String message) { UserContextHolder.UserInfo userInfo = UserContextHolder.getUserInfo(); String logContent = userInfo != null ? String.format("[UserID: %s, UserName: %s] %s", userInfo.getUserId(), userInfo.getUserName(), message) : message; logger.info(logContent); } // 可扩展warn、error等其他日志级别方法 }
方案三:扩展Keycloak Servlet过滤器注入用户信息
如果项目已配置Keycloak的Servlet过滤器,可直接扩展过滤器,在请求阶段将用户信息写入request属性:
public class CustomKeycloakFilter extends KeycloakAuthenticationProcessingFilter { public CustomKeycloakFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { KeycloakSecurityContext securityContext = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); if (securityContext != null) { String userId = securityContext.getToken().getSubject(); String userName = securityContext.getToken().getPreferredUsername(); request.setAttribute("userId", userId); request.setAttribute("userName", userName); } super.successfulAuthentication(request, response, chain, authResult); } }
注意:方案三需要确保项目已引入Keycloak Servlet相关依赖并配置基础过滤器,若未集成Spring Security,需调整过滤器的注册方式。
内容的提问来源于stack exchange,提问作者jane
相关产品推荐
相关产品推荐

