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

KeycloakSession对已认证用户为空,如何在事件监听器外获取该会话?

如何在Keycloak事件监听器之外获取非空的KeycloakSession?

我尝试通过以下代码获取KeycloakSession的内容,但KeycloakSession始终为空,无法打印其内容并在其他地方使用其部分属性:

@Context
public static KeycloakSession keycloakSession;

private static final Logger LOG = LoggerFactory.getLogger(userDetails.class);

public static void getSessionAttributes() {
    try {
        if (keycloakSession != null) {
            Map<String, Object> authenticatedUser = keycloakSession.getAttributes();
            for (Map.Entry<String, Object> entry : authenticatedUser.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();
                System.out.println("Key:" + key);
            }
        } else {
            System.out.println("keycloakSession is null");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

但当我在事件监听器的onEvent方法中放入类似代码时,KeycloakSession从未为空,我可以获取与当前登录用户关联的会话详情,如下代码始终有效:

public class userDetailsProvider implements EventListenerProvider
{
    @Context
    HttpServletRequest request;

    private final KeycloakSession keycloakSession;

    public userDetailsProvider(KeycloakSession keycloakSession)
    {
        this.keycloakSession = keycloakSession;
    }

    @Override
    public void onEvent(Event event)
    {
        String username=null;
        try {
            if (keycloakSession != null) {
                String authenticatedUser = keycloakSession.getContext().getAuthenticationSession().getAuthenticatedUser().getUsername();
                System.out.println("authenticatedUser from session:" + authenticatedUser);
            } else if (request.getParameterMap() != null) {
                Map<String, String[]> requestParameters = request.getParameterMap();
                for (Map.Entry<String, String[]> entry : requestParameters.entrySet()) {
                    String paramName = entry.getKey();
                    String[] paramValues = entry.getValue();
                    if ("username".equals(paramName)) {
                        username = paramValues[0];
                        System.out.println("Username:" + username);
                    }
                }
            } else {
                System.out.println("Username: Anonymous");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我使用的是Keycloak 21,应用作为OAuth2客户端,Keycloak配置为认证服务器。请问如何在事件监听器代码之外获取KeycloakSession,以便获取其属性?


问题原因分析

你第一段代码中KeycloakSession为空的核心原因:

  1. 静态成员无法被上下文注入:Keycloak的@Context注入机制仅对实例成员生效,静态字段不会被容器正确填充。
  2. 会话上下文的边界:事件监听器的实例由Keycloak在会话上下文内创建,通过构造函数传入的KeycloakSession天然属于当前有效会话;而你的静态写法脱离了Keycloak的会话管理生命周期。

可行解决方案

方案1:Keycloak服务器端扩展中正确获取会话

如果你的代码是Keycloak服务器的自定义扩展(如SPI、REST端点、自定义认证器等),采用以下标准方式:

方式A:构造函数注入(推荐)

像事件监听器一样,通过构造函数传入KeycloakSession,确保会话对象属于当前请求上下文:

public class CustomSessionService {
    private final KeycloakSession session;

    // 由Keycloak容器自动注入会话
    public CustomSessionService(KeycloakSession session) {
        this.session = session;
    }

    public void fetchSessionAttributes() {
        if (session != null) {
            Map<String, Object> attributes = session.getAttributes();
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                System.out.printf("Key: %s, Value: %s%n", entry.getKey(), entry.getValue());
            }
        }
    }
}
方式B:实例成员的@Context注入

在JAX-RS端点等支持上下文注入的组件中,使用实例成员而非静态成员:

@Path("/session-info")
public class SessionInfoEndpoint {
    @Context
    private KeycloakSession keycloakSession;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getCurrentUser() {
        if (keycloakSession != null) {
            return keycloakSession.getContext().getAuthenticationSession()
                    .getAuthenticatedUser().getUsername();
        }
        return "Unauthenticated";
    }
}

方案2:OAuth2客户端应用中获取用户信息(而非直接操作KeycloakSession)

如果你的代码是在外部OAuth2客户端应用中,直接访问Keycloak服务器内部的KeycloakSession是不可能的,此时应通过OAuth2协议规范获取用户数据:

  1. 从ID Token或Access Token中解析用户属性
  2. 调用Keycloak的UserInfo端点获取详细信息

示例(Spring Boot客户端):

@RestController
@RequestMapping("/user")
public class UserInfoController {
    @Autowired
    private OAuth2AuthorizedClientService clientService;

    @GetMapping("/details")
    public Map<String, Object> getUserDetails(OAuth2AuthenticationToken authToken) {
        OAuth2AuthorizedClient client = clientService.loadAuthorizedClient(
                authToken.getAuthorizedClientRegistrationId(),
                authToken.getName()
        );
        
        String userInfoUrl = client.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUri();
        
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(client.getAccessToken().getTokenValue());
        HttpEntity<Void> request = new HttpEntity<>(headers);
        
        return new RestTemplate().exchange(
                userInfoUrl,
                HttpMethod.GET,
                request,
                Map.class
        ).getBody();
    }
}

关键注意事项

  • 永远不要用静态成员接收KeycloakSession,这会破坏会话上下文的隔离性,且无法被正确注入。
  • 明确代码运行环境:是Keycloak服务器内部扩展,还是外部OAuth2客户端,两者的会话获取逻辑完全不同。

内容的提问来源于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 15:47:43