如何让Keycloak始终存储Broker Context并在登录后流程中传递给自定义SPI
我之前在做Keycloak SAML集成时也碰到过一模一样的问题——首次登录时能顺利拿到Broker Context,但后续登录的post-login流程里就拿不到了。后来才搞明白,Keycloak默认只会在用户首次关联身份提供商(IdP)的时候保留这个上下文,之后直接复用已有的用户会话,不会再把Broker Context存入认证会话的notes里。这里给你几个实用的解决思路,你可以根据自己的场景选:
方法1:把Broker Context持久化到用户属性中
既然首次登录时你能正常拿到Broker Context,那不如直接把它存在用户的自定义属性里,这样不管是首次还是后续登录,都能从用户属性里读取到。
在你SPI的首次登录逻辑里添加存储代码:
// 首次登录时获取Broker Context并存入用户属性 String brokerContextData = context.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE); UserModel user = context.getAuthenticationSession().getAuthenticatedUser(); if (brokerContextData != null && user.getFirstAttribute("broker_context") == null) { // 若上下文是字符串格式可直接存储,复杂结构可以先序列化 user.setSingleAttribute("broker_context", brokerContextData); // 别忘了更新用户信息到Keycloak UserManager userManager = new UserManager(context.getSession()); userManager.updateUser(context.getRealm(), user); }
之后在post-login流程里,直接从当前用户的属性中读取:
UserModel user = context.getUserSession().getUser(); String brokerContextData = user.getFirstAttribute("broker_context"); if (brokerContextData != null) { // 执行你的用户属性清理逻辑 }
这个方法的好处是一劳永逸,存一次之后每次都能拿到,适合Broker Context不会频繁变化的场景。
方法2:在Broker登录阶段手动复制到UserSession属性
如果你的Broker Context每次登录都可能有变化,不适合存在用户属性里,那可以把它复制到UserSession的属性中——UserSession在整个登录会话周期内都是有效的,post-login流程也能轻松访问到。
在你SPI的认证方法里(比如authenticate阶段),拿到Broker Context后存入UserSession:
String brokerContextData = context.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE); if (brokerContextData != null) { // 存入UserSession的自定义属性 context.getAuthenticationSession().getUserSession().setAttribute("CUSTOM_BROKER_CONTEXT", brokerContextData); }
然后在post-login的SPI实现里读取:
String brokerContextData = (String) context.getUserSession().getAttribute("CUSTOM_BROKER_CONTEXT"); if (brokerContextData != null) { // 执行你的业务逻辑 }
注意Keycloak的UserSession属性会跟着会话走,会话过期后就会消失,适合每次登录都需要最新Broker Context的场景。
方法3:扩展IdP认证器,强制每次登录都存入Broker Context
如果上面的方法都不符合你的需求,你可以自定义一个继承自AbstractIdpAuthenticator的认证器,重写它的action方法,在每次处理SAML断言后,手动把Broker Context存入认证会话的notes里,这样post-login流程就能像首次登录一样拿到它。
示例代码大概是这样:
public class CustomSamlIdpAuthenticator extends SamlIdpAuthenticator { @Override public void action(AuthenticationFlowContext context) { // 先执行父类的逻辑,处理SAML断言 super.action(context); // 从当前的IdP响应中提取Broker Context BrokeredIdentityContext brokeredContext = getBrokeredIdentityContext(context); if (brokeredContext != null) { // 把Broker Context序列化成字符串,存入认证会话的note ObjectMapper objectMapper = new ObjectMapper(); try { String contextJson = objectMapper.writeValueAsString(brokeredContext); context.getAuthenticationSession().setAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE, contextJson); } catch (JsonProcessingException e) { // 处理序列化异常 throw new RuntimeException("Failed to serialize broker context", e); } } } }
然后你需要在Keycloak里配置使用这个自定义的认证器,替换掉默认的SAML IdP认证器。这个方法最灵活,但需要你对Keycloak的认证流程有一定了解。
总的来说,最推荐的是方法1或方法2,根据你的Broker Context是否需要动态更新来选择,方法3适合有特殊定制需求的场景。
内容来源于stack exchange

