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为空的核心原因:
- 静态成员无法被上下文注入:Keycloak的
@Context注入机制仅对实例成员生效,静态字段不会被容器正确填充。 - 会话上下文的边界:事件监听器的实例由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协议规范获取用户数据:
- 从ID Token或Access Token中解析用户属性
- 调用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
相关产品推荐
相关产品推荐

