You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

未集成Keycloak Spring Security时,如何从UserModel获取用户信息加入自定义日志?

解决方案:未集成Spring Security时将Keycloak用户信息注入自定义日志机制

以下是几种可行的实现方式,根据你的项目场景选择:

方案一:ThreadLocal + Keycloak事件监听器传递用户信息

这种方式适合已通过Keycloak事件监听器获取UserModel的场景,利用ThreadLocal在请求线程中传递用户信息,再通过拦截器注入到request属性或直接供日志工具使用。

  1. 编写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; }
    }
}
  1. 在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() {
        // 资源清理操作
    }
}
  1. 自定义请求拦截器,将用户信息注入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();
    }
}
  1. 注册拦截器到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 22:05:29