UserSessionModel做为用户登录过程中的一个会话可以用来跨flow使用数据这些数据被保存到内存里在认证过程中可以被使用今天的一个需求要求在登录时从请求头获取IP所在地并写到kafka里要想实现这个需求你可以在现有认证流程中修改代码但不建议这样做因为这种修改对原始逻辑会有破坏keycloak提供了自定义认证流并在后台可以灵活的配置。相关keycloak中的知识认证流程的执行动作从上面图中可以看到这个登录的过程会经历多个认证流在所有被开启的认证流执行完成后才算登录成功而这些流程我们是可以进行按需开发并配置的下面说一下keycloak认证过程的几大事件以表单登录为例社区三方认证流程更复杂一些表单提交标准用户密码认证流执行扩展认证流执行会话限制 User Session Count Limiter请求头到session的转换 Header-session-authenticator黑名单控制 BlackListFilterAuthenticator用户有效性控制 User Validate弱密码提醒 Config Simple Password Alert FormMFA多因子认证 OTP Form执行jwt token构建流程包含自定义的AbstractOIDCProtocolMapper等发布Login登录成功事件订阅了Login事件的监听器可以写入kafka消息keycloak认证流程相关元素浏览器认证流Browser Flow 继承AbstractUsernameFormAuthenticator类直接认证流Direct Grant Flow 继承BaseDirectGrantAuthenticator类用户所需要动作Require Action 实现RequiredActionProvider接口表单页面Form Action实现了FormAction接口关于登录事件Login扩展的分析在AbstractUsernameFormAuthenticator实现类中添加登录扩展参数定义一个认证流专业处理这块请求头参数并把它放入登录事件定义一个Login事件监听器在收到后信息后可进行中间件转发如果需要扩展token中的属性也可以通过扩展认证流来实现将数据存入userSession并且在AbstractOIDCProtocolMapper阶段读取userSession的内容写入到jwtToken中实现步骤下面自定义一个从请求头获取属性写入userSessionModel的例子JBossLog public class RequestHeaderToSessionNoteAuthenticator implements Authenticator { private final KeycloakSession session; public RequestHeaderToSessionNoteAuthenticator(KeycloakSession session) { this.session session; } Override public void authenticate(AuthenticationFlowContext context) { HttpHeaders httpHeaders context.getHttpRequest().getHttpHeaders(); if (httpHeaders.getRequestHeaders().containsKey(UserUtils.EO_CLIENT_REGIONNAME)) { context.getAuthenticationSession().setUserSessionNote(lastLoginProvince, URLDecoder.decode(httpHeaders.getHeaderString(UserUtils.EO_CLIENT_REGIONNAME))); context.getEvent().detail(lastLoginProvince, URLDecoder.decode(httpHeaders.getHeaderString(UserUtils.EO_CLIENT_REGIONNAME))); } if (httpHeaders.getRequestHeaders().containsKey(UserUtils.EO_CLIENT_CITYNAME)) { context.getAuthenticationSession().setUserSessionNote(lastLoginCity, URLDecoder.decode(httpHeaders.getHeaderString(UserUtils.EO_CLIENT_CITYNAME))); context.getEvent().detail(lastLoginCity, URLDecoder.decode(httpHeaders.getHeaderString(UserUtils.EO_CLIENT_CITYNAME))); } context.success(); } private EntityManager getEntityManager() { return this.session.getProvider(JpaConnectionProvider.class).getEntityManager(); } Override public void action(AuthenticationFlowContext context) { } Override public boolean requiresUser() { return false; } Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return false; } Override public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } } public class RequestHeaderToSessionNoteAuthenticatorFactory implements AuthenticatorFactory, ConfigurableAuthenticatorFactory { public final static String PROVIDER_ID header-session-authenticator; Override public String getDisplayType() { return header-session-authenticator; } Override public String getReferenceCategory() { return null; } Override public boolean isConfigurable() { return false; } Override public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { return REQUIREMENT_CHOICES; } // 是否针对用户有require action动作如果没有requiresUser()返回也为false Override public boolean isUserSetupAllowed() { return false; } Override public String getHelpText() { return header-session-authenticator; } Override public ListProviderConfigProperty getConfigProperties() { return null; } Override public Authenticator create(KeycloakSession keycloakSession) { return new RequestHeaderToSessionNoteAuthenticator(keycloakSession); } Override public void init(Scope scope) { } Override public void postInit(KeycloakSessionFactory keycloakSessionFactory) { } Override public void close() { } Override public String getId() { return PROVIDER_ID; }