keycloak~keycloak14.0源代码二次开辟

[复制链接]
发表于 2025-10-31 12:27:26 | 显示全部楼层 |阅读模式
本地调试入口


编译keycloak源代码某个包
  1. mvn package -Denforcer.skip=true -Dmaven.test.skip=true
  2. mvn clean install -Dskip=true
复制代码

  • 摆设到私服,发起源码修改后,应该摆设到私服,如许别的应用在摆设时,也有可以利用修改后的代码
  1. $ mvn deploy -Denforcer.skip=true -Dmaven.test.skip=true
复制代码
当用户已经在欣赏器登录,在利用自动登录接口(大概之前同时打开两个登录页)这时为了包管用户的登录状态,反面的登录哀求会被拦截,跳转到“首页”


  • 如果非iframe的情况,利用下面的代码可以实现标题,在org.keycloak.services.resources.LoginActionsServiceChecks
    .checkNotLoggedInYet()和SessionCodeChecks.initialVerifyAuthSession()方法添加302跳转
  • 对于iframe内里的登录页,必要思量怎样在顶级窗口实现重定向,现在添加js重定向办理了这个iframe标题
  1. // TODO: 当用户已经登录了,直接跳到首页
  2. Response.status(302).header(HttpHeaders.LOCATION, "https://www.xxx.com").build();
复制代码


js重定向办理了同时多个iframe登录框,在此中一个登录,另一个在本iframe重定向标题


修改url中有特殊符号的标题

修改org.keycloak.protocol.oidc.utils.RedirectUtils.removeUrlSpaceParams方法,将特殊符号举行编码

天生token时添加日志日志


修改code to token的缓存范例

默认利用BasicCache,应该是本地缓存,查通过检察TokenEndpoint,发现是分布式缓存

LOGIN事故的个性化设置

org.keycloak.services.managers.AuthenticationManager的方法nextRequiredAction和actionRequired,添加了LOGIN事故的个性化字段

code_to_token时,去掉了clientId限定


  • code_to_token时,去掉了clientId必须同等的条件的查验,如许差异客户端在通过code换token时,可以镌汰与kc交互交次数
  • 方法变更:TokenEndpoint.codeToToken()

code_to_token时对欣赏器sessionId利用


  • 当code to token出现错误时,添加了清空欣赏器里sessionId在kc的会话信息,但如果是httpclient的调用,咱们是拿不到客户端欣赏器的cookie的
  • Code '%s' already used for userSession
    org.keycloak.protocol.oidc.util.OAuth2CodeParser.parseCode这块添加了clientId的日志日志形貌

修改kc管理背景session列表由于信息不全报错的标题


  • 告急是ModelToRepresentation报错了,应该是client_id为空引起的,像refresh_token到达次数会引起会话的client_id为空,但sessionId照旧在线的。
  • 这块将非常报错复原了,如果不报错,将会出现大量session从数据库加载,导致数据库崩盘

验证token去掉协议名的限定


  • 对在线token的校验,去掉了https和http的校验,只要反面域名类似就是ISS(Issuer)类似就行,这块在验证token时会用到,别的在适配器集成中,每人哀求加载前,也会用到它


观察code码被占用标题(生产了重复的code码)

1 code的生产

2 code的校验


  • 办理同时打开两个登录窗口,在第一个窗口登录后,在第二个窗口再登录一次,会出现“您已经登录”的页面
  • 办理:发生上面的情况后,直接跳到v6首页

    • LoginActionsServiceChecks.checkNotLoggedInYet()方法
    • SessionCodeChecks.initialVerifyAuthSession()方法

让IdentityProviderMapper实现的范例,自动为社区登录实行delegateUpdateBrokeredUser


  • 修改源代码:org.keycloak.services.resources.updateFederatedIdentity()
  • 添加了代码逻辑,实现了按需自动实行
  1.      //对已有用户进行更新,注意,可能会覆盖用户的其它属性
  2.     FederatedIdentityModel finalFederatedIdentityModel = federatedIdentityModel;
  3.     sessionFactory.getProviderFactoriesStream(IdentityProviderMapper.class)
  4.         .map(IdentityProviderMapper.class::cast)
  5.         .map(mapper -> Arrays.stream(mapper.getCompatibleProviders())
  6.             .filter(type -> Objects.equals(finalFederatedIdentityModel.getIdentityProvider(), type))
  7.             .map(type -> mapper)
  8.             .findFirst()
  9.             .orElse(null))
  10.         .filter(Objects::nonNull)
  11.         .collect(Collectors.toMap(IdentityProviderMapper::getId, Function.identity()))
  12.         .forEach((a, b) -> {
  13.           IdentityProviderMapper target = (IdentityProviderMapper) sessionFactory
  14.               .getProviderFactory(IdentityProviderMapper.class, a);
  15.           IdentityProviderMapperModel identityProviderMapperModel = new IdentityProviderMapperModel();
  16.           identityProviderMapperModel.setConfig(new HashMap<>());
  17.           identityProviderMapperModel.setSyncMode(IdentityProviderMapperSyncMode.FORCE);
  18.           identityProviderMapperModel.setId(a);
  19.           identityProviderMapperModel.setIdentityProviderMapper(finalFederatedIdentityModel.getIdentityProvider());
  20.           identityProviderMapperModel.setIdentityProviderAlias(finalFederatedIdentityModel.getIdentityProvider());
  21.           try {
  22.             if (!Objects.equals(target.getId(), UsernameTemplateMapper.PROVIDER_ID)) {
  23.               IdentityProviderMapperSyncModeDelegate.delegateUpdateBrokeredUser(session, realmModel, federatedUser,
  24.                   identityProviderMapperModel,
  25.                   context, target);
  26.             }
  27.           } catch (RuntimeException ex) {
  28.           }
  29.         });
复制代码

  • 添加一个例子,实现社区登录的范例自动存储到用户属性loginType中,getCompatibleProviders()方法中绑定了
    IdentityProviderMapper.ANY_PROVIDER,以是在每个社区登录后,它都会被实行
  • 新用户不会绑定这个,已绑定的用户才实行这个方法,缘故起因是syncModel为Force,表现当有效户后,会欺凌更新它
  1. public class V6UserAttributeMapper extends AbstractJsonUserAttributeMapper {
  2.   public static final String PROVIDER_ID = "v6-user-attribute-mapper";
  3.   private static final String[] cp = new String[] {IdentityProviderMapper.ANY_PROVIDER};
  4.   @Override
  5.   public String[] getCompatibleProviders() {
  6.     return cp;
  7.   }
  8.   @Override
  9.   public String getId() {
  10.     return PROVIDER_ID;
  11.   }
  12.   @Override
  13.   public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user,
  14.                                  IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
  15.     logger.info("updateBrokeredUser user info...");
  16.     user.setSingleAttribute("loginType", mapperModel.getIdentityProviderAlias());
  17.   }
  18. }
复制代码

  • 登录后更新用户属性loginType

登录后,将loginType添加到refresh_token中

办理由于kc的refresh_token不支持自界说属性,以是在登录后,将loginType添加到refresh_token中,如许在refresh_token时,就可以获取到loginType了


  • 实现逻辑:将当前loginType添加到当前refresh_token,在下次革新token时,将refresh_token里的loginType取出来,覆盖到新的access_token里.
  • org.keycloak.protocol.oidc.TokenManager.validateToken()
  • org.keycloak.protocol.oidc.TokenManager.build()
用户session_state天生方式


  • org.keycloak.models.sessions.infinispan.createUserSession()

办理用户欣赏器由于丢失keycloak_identity而keycloak_session_id有而且是在线的,导致无法登录的标题


  • 在方法AuthorizationEndpointBase.createAuthenticationSession()添加了判断逻辑,没有keycloak_identity就重新根据session_id再天生一个到cookie里
  1. Cookie cookie = CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE);
  2. if (cookie == null) {
  3.   cookie =
  4.       CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE + CookieHelper.LEGACY_COOKIE);
  5.   if (cookie == null) {
  6.     AuthenticationManager.createLoginCookie(session, realm, user, userSession, session.getContext().getUri(),
  7.         session.getContext().getConnection());
  8.   }
  9. }
复制代码

  • 关于对loginType和登录事故的修改
    请检察TODO: 20230406的表明代码
  • 涉及到以下动作会触发的事故,会添加我们扩展的属性

    • 共享登录
    • code换token
    • 革新token

关于OTP提供商的调研


  • OTP提供商的战略:org.keycloak.models.OTPPolicy,现在支持FreeOTP和GoogleAuthenticator

关于keycloak-services项目添加第三方jar包的标题

我们比方将org.infinispan这个包,在kc里也是一个module,引用到keycloak-services项目,它在启动时会报错,告诉找不到这个org.infinispan.Cache类,类似这种类无法找到的错误。
  1. Uncaught server error: java.lang.NoClassDefFoundError: org/infinispan/Cache
  2.         at org.keycloak.keycloak-services@14.0.0//org.keycloak.protocol.oidc.TokenManager.checkTokenValidForIntrospection(TokenManager.java:494)
复制代码
办理思绪,在module.xml中,添加对应的模块即可


从keycloak容器里将/opt/jboss/keycloak/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml复制出来,在文件的dependencies节点下添加依赖,如


  • 修改Dockerfile文件,将这个module.xml文件也复制到上面的容器目次,覆盖原来的文件
  • 重新构建镜像,启动容器,标题办理
自动登录接口同一欣赏器添加踢出之前登录的逻辑


  • org.keycloak.protocol.AuthorizationEndpointBase.handleBrowserAuthenticationRequest()

从carsi网站过来的用户,会带有carsi-auto这个关键字,也应该要踢出之前的登录


  • org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider.Endpoint.authResponse()

革新token和通过code换token逻辑中,添加infinispan缓存的逻辑,键key是sessionId+clientId,作用是当用户的脚色变更后,用户校验token直接返回false,让迫利用户重新去革新token

// TODO: xxx_user_modify_role 必要添加逻辑,去检索事故中是否包罗了权限变更的用户


  • 验证token: org.keycloak.protocol.oidc.TokenManager.checkTokenValidForIntrospection()
  • 革新token: org.keycloak.protocol.oidc.endpoints.TokenEndpoint.refreshTokenGrant
    ,这块由于类似的event对象,以是代码迁徙到keycloak-services-event-kafka
  • code换token: org.keycloak.protocol.oidc.endpoints.TokenEndpoint.codeToToken()
    ,这块由于类似的event对象,以是代码迁徙到keycloak-services-event-kafka
用户权限更新后,通和逻辑整理

  • kc服务端收到REALM_ROLE_MAPPING大概USER_ROLE_CHANGE事故后,向infinispan缓存里添加一个key,key是
    xxx_user_modify_role_{userId},value是空
  • 它有缓存有效期与access_token的类似,现在为30分钟
  • 当用户举行code换token大概革新token时,根据当前用户id,去上面缓存中找,如果查找到,阐明这个用户的权限发生了变更
  • 找到后,向这个缓存xxx_user_modify_role_{userId}添加value,value格式是{sessionId}_{clientId},就是用户在哪个欣赏器
    哪个客户端访问
  • 当用户调用验证token接口时,如果在xxx_user_modify_role_{userId}中没有找到这个value{sessionId}_{clientId},就验证失败
  • 当验证失败后,返回401,用户再去革新token,向xxx_user_modify_role_{userId}中添加对应的value, 保持下次验证会乐成
获取IP所在的方法修改

// TODO: 优化登录事故中,获取ipAddress的逻辑,改为real-ip有限

  • org.keycloak.events.EventBuilder.ipAddress()举行了重新赋值
  • org.keycloak.services.resources.admin.AdminEventBuilder.AdminEventBuilder()初始化时,利用real-ip

验证token逻辑的抛下,分析session idle和session max的逻辑


  • org.keycloak.services.managers.AuthenticationManager.isSessionValid
  • SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS是个时间戳口,它是120秒
  1.   public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
  2.     if (userSession == null) {
  3.         logger.debug("No user session");
  4.         return false;
  5.     }
  6.     int currentTime = Time.currentTime();
  7.     // Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
  8.     int maxIdle = userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ?
  9.             realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout();
  10.     int maxLifespan = userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
  11.             realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();
  12.     boolean sessionIdleOk =
  13.             maxIdle > currentTime - userSession.getLastSessionRefresh() - SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
  14.     boolean sessionMaxOk = maxLifespan > currentTime - userSession.getStarted();
  15.     return sessionIdleOk && sessionMaxOk;
  16. }
复制代码
session idle和session max的逻辑收效,如果修改refresh_token天生时的校验逻辑
  1. org.keycloak.protocol.oidc
  2. .TokenManager.refreshAccessToken()方法中的代码,将verifyRefreshToken方法参数中的checkExpiration改成false
  3. // TODO: 完善实现了在线校验时,session idle和session max的功能
复制代码
session idle(空闲逾期时间)和session max(最大逾期时间)不相称时,产生的标题

形貌与办理思绪


  • session idle会作为革新token的逾期时间
  • 当这个时间到达后,不能再革新token了,但是,session照旧在线的
  • 是否必要在到达这个时间后,将会话删除?
  • 如果真要删除的话,大概产生的标题就是session max的时间还没到,但是session已经被删除了,如许就会导致session max的时间不精确了
  • 但如果session idle到达,而且token没有乐成革新,这阐明用户空闲了,这时session是可以删除的,与4不抵牾
  • 办理方法
    *[x] 在session idle到达后,将session删除,应该就办理标题了
    *[x] 大概在天生code之前,判断它的session idle是否到期,如果到期,将会话删除,不能天生code
用户会话逾期,清算用户会话的逻辑调解



  • org.keycloak.services.scheduled.ScheduledTaskRunner # 默认900秒(15分钟)实行一次

    • org.keycloak.services.scheduled.ClearExpiredUserSessionsTask

      • org.keycloak.models.map.authSession.removeExpired
      • 必要添加空闲逾期时间的判断,如果到期,就删除会话
      • 这块必要看怎样手动扫除,由于默认的,infinispan中的session,有本身的逾期时间,按着逾期时间自动扫除的
      • 咱们相称于,怎样让它按着咱们的时间(这个时间颠末了session idle的时间)来扫除的


  • 通过上面的分析,直接从infinispan中获取逾期的session,并删除不太大概,人家关照初始化的逾期时间自行维护的,而且这种逾期时间,是session
    max,而咱们的逾期时间是可变的,它大概是一个session idle,也大概是session max,这与用户是否在idle时间内是否有利用有关


  • 这种定时器,大概是为了mysql中存储的离线token用的,可检察offline_user_session干系的内容
  • 我们如果在天生code时,添加一个判断,判断这个session idle是否到期,如果到期,就删除会话,不能天生code
天生code时,添加session idle的判断


  • 如果不添加这个判断,将会出现的标题是,当session idle和session max设置差异时,当session
    idle到期后,用户的会话不会被删除,导致革新token和申请code码换token,两块产生的token逻辑不一样,终极效果就是,code可以换返来token,但token在校验时是
    session not active如许的错误。
  • org.keycloak.protocol.AuthorizationEndpointBase.createAuthenticationSession()方法

修改org.keycloak.theme.DefaultThemeSelectorProvider文件getThemeName()方法,添加了url中皮肤参数theme
  1. MultivaluedMap<String, String> query = session.getContext().getUri().getQueryParameters();
  2. if(query.
  3. containsKey("theme")){
  4. name=query.
  5. getFirst("theme");
  6. }else{
  7.         }
复制代码
登录跨域支持

org.keycloak.protocol.oidc.endpoints.TokenEndpoint.resourceOwnerPasswordCredentialsGrant(),返回值添加跨域代码
  1. return cors.builder(Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).
  2. allowAllOrigins().
  3. build();
复制代码
登录回调所在中添加loginType这个参数


  • org.keycloak.services.resources.IdentityBrokerService.finishBrokerAuthentication()方法添加对loginType的利用
  • org.keycloak.protocol.oidc.OIDCLoginProtocol.authenticated()方法中,获取loginType,并添加到回调路径的URL参数中


社区登录乐成后,绑定用户信息,修改FEDERATED_IDENTITY_LINK的内容


  • org.keycloak.services.resources.IdentityBrokerService.afterFirstBrokerLogin方法
  • 添加自界说事故元素:event.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getBrokerUserId());
革新token时,如果用户有required action,抛出非常


  • org.keycloak.protocol.oidc.TokenManager.validateToken()方法
  1.     //TODO:刷新token时,如果用户有required action,抛出异常
  2.     if (user.getRequiredActionsStream().findAny().isPresent()) {
  3.       throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User has required action",
  4.           "User has required action");
  5.     }
复制代码
社区登录state的自界说

社区登录回调state参数,支持4个参数


  • org.keycloak.broker.provider.util.IdentityBrokerState类中encoded方法,将
    String[] decoded = DOT.split(encodedState, 4);从3改成4

创建社区登录所在时添加自界说state参数


  • AbstractOAuth2IdentityProvider类中createAuthorizationUrl方法,修改state参数的拼接
  1. String state = request.getState().getEncoded();
  2.     if(request.
  3. getAuthenticationSession().
  4. getAuthNote("g") !=null&&
  5.         request.
  6. getAuthenticationSession().
  7. getAuthNote("g").
  8. trim() !=""){
  9. state =state +"."+request.
  10. getAuthenticationSession().
  11. getAuthNote("g");
  12.     }
复制代码

在认证乐成后federatedIdentityContext上下文添加参数


  • AbstractOAuth2IdentityProvider类中Endpoint.authResponse方法,再返回之前为federatedIdentity添加groupId参数
  1. // 添加集团代码
  2. String[] decoded = DOT.split(state, 4);
  3. if(decoded.length ==4){
  4.         federatedIdentity.
  5. setUserAttribute("groupId",decoded[3]);
  6. }
复制代码

登录超时的提示语调解


  • keycloak-themes/themes/base/login/messages/messages_en.properties文件
  • 修改loginTimeout的值即可
社区登录页{provider}/login页添加idp和login_type参数


  • org.keycloak.services.Urls类identityProviderAuthnRequest()方法,添加idp参数的追加
  • org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator类中redirect()
    方法构建登录页重定向参数,添加loginType和idp两个参数

/auth/realms/xxx/protocol/openid-connect/userinfo接口添加session_state属性返回值


  • org.keycloak.protocol.oidc.OIDCLoginProtocolService类中的issueUserInfo()方法
  • 添加跨域支持allowedOrigins("*")
  • 分析当前token,并添加session_state
  1. claims.put("session_state", userSession.getId());// 添加当前的session信息
复制代码
革新token时,出现Session not active大概Invalid refresh token


  • Session not active 表现用户的session已经逾期了,必要重新登录,返回400
  • Invalid refresh token 表现refresh token不精确,大概token被窜改了,必要重新登录,返回400
  • 革新token时,只有一种情况会返回401,就是client_secret错误时,Client secret not provided in request
  • org.keycloak.protocol.oidc.TokenEndpoint.refreshTokenGrant()方法,添加refresh_token验证不通过,会走这个catch逻辑,大多数情况httpcode都是400
  1. catch (OAuthErrorException e) {
  2.       logger.trace(e.getMessage(), e);
  3.       // KEYCLOAK-6771 Certificate Bound Token
  4.       if (MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC.equals(e.getDescription())) {
  5.         event.error(Errors.NOT_ALLOWED);
  6.         throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.UNAUTHORIZED);
  7.       } else {
  8.         event.error(Errors.INVALID_TOKEN);
  9.         throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
  10.       }
复制代码
社区登录添加loginType为社区的idp


  • 情况一,未绑定用户,走first flow
  • 情况二,绑定了用户,下次再登录,会走after flow
first flow

org.keycloak.services.resources.IdentityBrokerService.authenticated()方法,添加代码
  1. authenticationSession.setUserSessionNote("loginType",context.getIdpConfig().getProviderId());
复制代码

after flow

org.keycloak.services.resources.IdentityBrokerService.authenticated()方法,添加代码
  1. authenticationSession.setUserSessionNote("loginType",context.getIdpConfig().getProviderId());
复制代码

社区登录microsoft修改FEDERATED_IDENTITY_LINK的BUG


  • 用户第一次利用社区来绑定本地KC用户时,必要为社区用户的unionId赋值到BrokeredIdentityContext对象
  • 在MicrosoftIdentityProvider.extractIdentityFromProfile()方法,添加了 user.setBrokerUserId(id);
  • 如果别的社区登录必要集成,也必要手动添加上面的代码
  • IdentityBrokerService.afterFirstBrokerLogin()方法,添加用户第一次绑定社区时FEDERATED_IDENTITY_LINK的扩展信息
管理背景-用户检索-改为用户名准确大概邮箱准确


  • org.keycloak.models.jpa.JapUserProvider类
  • searchForUserStream(RealmModel realm, Map attributes, Integer firstResult, Integer maxResults)方法
个人中心绑定社区用户

代码表明,去掉权限的控制:IdentityBrokerService.clientInitiatedAccountLinking()方法,注册下面代码

  • 堕落信息:not_allowed
  1. //      if (!userAccountRoles.contains(manageAccountRole)) {
  2. //        RoleModel linkRole = accountService.getRole(AccountRoles.MANAGE_ACCOUNT_LINKS);
  3. //        if (!userAccountRoles.contains(linkRole)) {
  4. //          event.error(Errors.NOT_ALLOWED);
  5. //          UriBuilder builder = UriBuilder.fromUri(redirectUri)
  6. //              .queryParam(errorParam, Errors.NOT_ALLOWED)
  7. //              .queryParam("nonce", nonce);
  8. //          return Response.status(302).location(builder.build()).build();
  9. //        }
  10. //      }
复制代码

  • 堕落信息:insufficientPermissionMessage
  1. //    if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)
  2. //        .getRole(AccountRoles.MANAGE_ACCOUNT))) {
  3. //      return redirectToErrorPage(authSession, Response.Status.FORBIDDEN, Messages.INSUFFICIENT_PERMISSION);
  4. //    }
复制代码

  • 修改非account客户端的错误页逻辑,直接将错误编码带着泉源页
  • IdentityBrokerService.redirectToErrorWhenLinkingFailed()
  1. private Response redirectToErrorWhenLinkingFailed(AuthenticationSessionModel authSession, String message,
  2.                                                    Object... parameters) {
  3.     if (authSession.getClient() != null &&
  4.             authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
  5.         return redirectToAccountErrorPage(authSession, message, parameters);
  6.     } else {
  7.         //  return redirectToErrorPage(authSession, Response.Status.BAD_REQUEST, message, parameters); // Should rather redirect to app instead and display error here?
  8.         // 当出现错误,将错误消息直接带到来源页
  9.         URI errUrl =
  10.                 UriBuilder.fromUri(authSession.getRedirectUri()).queryParam("error", message).build();
  11.         return Response.status(302).location(errUrl).build();
  12.     }
  13. }
复制代码
初次登录社区,并完成老用户的绑定,向FEDERATED_IDENTITY_LINK事故添加corpId


  • org.keycloak.services.resources.IdentityBrokerService.afterFirstBrokerLogin(AuthenticationSessionModel authSession)
    方法中添加代码
  1. event.detail(CORP_ID,context.getUserAttribute(CORP_ID)); // 这块与认证页有跨页,所以authSession.getAuthNote(CORP_ID)无法获取到corpId,所以临时存在userAttribute对应的内存中,并存持久化到数据库
复制代码

  • 具体的AbstractOAuth2IdentityProvider子类中Endpoint.authResponse()方法中添加代码
  1. //11.3.0之后改成这样了,去掉了code字段
  2. federatedIdentity.setUserAttribute("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));
  3. // authSession.setAuthNote("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));
复制代码
已登录的用户去绑定社区用户时,向FEDERATED_IDENTITY_LINK事故添加corpId


  • org.keycloak.services.resources.IdentityBrokerService.performAccountLinking()方法中添加代码
  • 这块为了同一,也利用getUserAttribute即可
  1. this.event.user(authenticatedUser)
  2.     .detail(Details.USERNAME, authenticatedUser.getUsername())
  3.     .detail(Details.IDENTITY_PROVIDER, newModel.getIdentityProvider())
  4.     .detail(Details.IDENTITY_PROVIDER_USERNAME, newModel.getUserName())
  5.     .detail(CORP_ID,federatedIdentity.getUserAttribute(CORP_ID))// 从已经登录的用户点社区登录,绑定事件中添加corpId
  6.     .success();
复制代码
parseSessionCode报错

AuthenticationSessionManager.getCurrentAuthenticationSession authSessionCookies这块添加日志日志,看是否kc可以获取到欣赏器cookie中的auth_session_id,如果获取不到会出现下面错误
  1. ERROR [org.keycloak.services.resources.IdentityBrokerService] (default task-1709) unexpectedErrorHandlingRequestMessage: javax.ws.rs.WebApplicationException: HTTP 400 Bad Request
  2.         at org.keycloak.keycloak-services@14.0.0//org.keycloak.services.resources.IdentityBrokerService.parseSessionCode(IdentityBrokerService.java:1225)
  3.         at org.keycloak.keycloak-services@14.0.0//org.keycloak.services.resources.IdentityBrokerService.performLogin(IdentityBrokerService.java:419)
  4.         at jdk.internal.reflect.GeneratedMethodAccessor673.invoke(Unknown Source)
  5.         at java.base/jdk.internal.reflect.DelegatingMethodAccessorImp
复制代码
auth_session_id分析过程


  • auth_session_id它是由session_state.nodeId构成的,session_state是用户会话的id,nodeId是kc集群节点的标识符,它们之间用点号分隔开,比如
    5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1,如许在集群情况下,可以将哀求路由到对应的节点上去。
  • KEYCLOAK_IDENTITY它是用户登录之后产生的,它是一个jwt的token,包罗最底子的会话信息
  1. eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTFiNDI0OC02OGZjLTQwNDQtYjM4Ny1kMGNjOTI3ZWI1MmIifQ.eyJleHAiOjE3NjE5MTY2OTgsImlhdCI6MTc2MTg4MDY5OCwianRpIjoiYTIwNDFjNTgtZmE5NC00MDA4LTg3YzEtZTI1MWEwMmZmNjk2IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNC4yNjo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjZlMjZjNGQwLTZiMzktNDllYy1hNWE0LWI3MzBkOTA3ZjM3ZiIsInR5cCI6IlNlcmlhbGl6ZWQtSUQiLCJzZXNzaW9uX3N0YXRlIjoiOGI5YjgyMDUtMTcyYi00YzFiLWFmNzYtNGI1Yjk4ZTE4YzY4Iiwic3RhdGVfY2hlY2tlciI6InNGdDkxOFBWcnFDaGxWNV8wYm5RY0pxZVJ2dlYyS3hQbU9lRTBfV3dPRjQifQ.KRViHyjY54UhswmXnCCMpSRY9SoV2k3yANXfUtQpLvc
  2. {
  3.     "exp": 1761916698,
  4.     "iat": 1761880698,
  5.     "jti": "a2041c58-fa94-4008-87c1-e251a02ff696",
  6.     "iss": "http://192.168.4.26:8080/auth/realms/master",
  7.     "sub": "6e26c4d0-6b39-49ec-a5a4-b730d907f37f",
  8.     "typ": "Serialized-ID",
  9.     "session_state": "8b9b8205-172b-4c1b-af76-4b5b98e18c68",
  10.     "state_checker": "sFt918PVrqChlV5_0bnQcJqeRvvV2KxPmOeE0_WwOF4"
  11. }
复制代码

  • 天生一个auth_session_id到欣赏器cookie中
  • IdentityBrokerService.clientInitiatedAccountLinking()方法中,调用
    AuthenticationSessionManager.getCurrentAuthenticationSession()方法,分析欣赏器cookie中的auth_session_id
  1. AuthenticationManager.AuthResult cookieResult =
  2.         AuthenticationManager.authenticateIdentityCookie(session, realmModel, true);
  3. //...
  4. AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
  5. // Refresh the cookie
  6. new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);
复制代码


  • AuthenticationManager类中的authenticateIdentityCookie用来天生一个AuthResult对象,如果用户已经登录(有KEYCLOAK_IDENTITY_COOKIE)这个auth_session_id就会被利用
  1. public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
  2.    Cookie cookie =
  3.    CookieHelper.getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_IDENTITY_COOKIE);
  4.    if (cookie == null || "".equals(cookie.getValue())) {
  5.    logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
  6.    return null;
  7.    }
  8.    String tokenString = cookie.getValue();
  9.    AuthResult authResult =
  10.    verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(),
  11.    checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(),
  12.    VALIDATE_IDENTITY_COOKIE);
  13.    if (authResult == null) {
  14.    expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
  15.    expireOldIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
  16.    return null;
  17.    }
  18.    authResult.getSession().setLastSessionRefresh(Time.currentTime());
  19.    return authResult;
  20.    }
复制代码

  • AuthenticationSessionManager文件
  1.     /**
  2.      * @param authSessionId decoded authSessionId (without route info attached)
  3.      * @param realm
  4.      */
  5.     public void setAuthSessionCookie(String authSessionId, RealmModel realm) {
  6.         UriInfo uriInfo = session.getContext().getUri();
  7.         String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);
  8.         boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
  9.         StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class);
  10.         String encodedAuthSessionId = encoder.encodeSessionId(authSessionId);
  11.         CookieHelper.addCookie(AUTH_SESSION_ID, encodedAuthSessionId, cookiePath, null, null, -1, sslRequired, true, SameSiteAttributeValue.NONE);
  12.         log.debugf("Set AUTH_SESSION_ID cookie with value %s", encodedAuthSessionId);
  13.     }
  14.     /**
  15.      *
  16.      * @param encodedAuthSessionId encoded ID with attached route in cluster environment (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1" )
  17.      * @return object with decoded and actually encoded authSessionId
  18.      */
  19.     AuthSessionId decodeAuthSessionId(String encodedAuthSessionId) {
  20.         log.debugf("Found AUTH_SESSION_ID cookie with value %s", encodedAuthSessionId);
  21.         StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class);
  22.         String decodedAuthSessionId = encoder.decodeSessionId(encodedAuthSessionId);
  23.         String reencoded = encoder.encodeSessionId(decodedAuthSessionId);
  24.         return new AuthSessionId(decodedAuthSessionId, reencoded);
  25.     }
复制代码


  • 我在获取auth_session_id的代码段添加日志后,发现在跨域iframe对接kc时,kc服务端无法获取到auth_session_id,以是末了导致出现parseSessionCode


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表