中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

keycloak~keycloak14.0源代碼二次開發

本地調試入口

圖片

編譯keycloak源代碼某個包

mvn package -Denforcer.skip=true -Dmaven.test.skip=true
mvn clean install -Dskip=true
  • 部署到私服,建議源碼修改后,應該部署到私服,這樣其它應用在部署時,也有可以使用修改后的代碼了
$ mvn deploy -Denforcer.skip=true -Dmaven.test.skip=true

當用戶已經在瀏覽器登錄,在使用自動登錄接口(或者之前同時打開兩個登錄頁)這時為了保證用戶的登錄狀態,后面的登錄請求會被攔截,跳轉到“首頁”

  • 如果非iframe的情況,使用下面的代碼可以實現問題,在org.keycloak.services.resources.LoginActionsServiceChecks
    .checkNotLoggedInYet()和SessionCodeChecks.initialVerifyAuthSession()方法添加302跳轉
  • 對于iframe里面的登錄頁,需要考慮如何在頂級窗口實現重定向,目前添加js重定向解決了這個iframe問題
// TODO: 當用戶已經登錄了,直接跳到首頁
Response.status(302).header(HttpHeaders.LOCATION, "//www.xxx.com").build();

圖片

圖片

js重定向解決了同時多個iframe登錄框,在其中一個登錄,另一個在本iframe重定向問題

圖片

修改url中有特殊符號的問題

修改org.keycloak.protocol.oidc.utils.RedirectUtils.removeUrlSpaceParams方法,將特(te)殊符(fu)號進行編碼

圖片

生成token時添加日志

圖片

修改code to token的緩存類型

默認使用(yong)BasicCache,應該是(shi)本地(di)緩存(cun),查(cha)通過查(cha)看(kan)TokenEndpoint,發現是(shi)分(fen)布式緩存(cun)

圖片

LOGIN事件的個性化配置

org.keycloak.services.managers.AuthenticationManager的(de)方法nextRequiredAction和actionRequired,添加了LOGIN事件的(de)個(ge)性化字段

圖片

code_to_token時,去掉了clientId限制

  • code_to_token時,去掉了clientId必須一致的條件的檢驗,這樣不同客戶端在通過code換token時,可以減少與kc交互交次數
  • 方法變更:TokenEndpoint.codeToToken()
    圖片

code_to_token時對瀏覽器sessionId操作

  • 當code to token出現錯誤時,添加(jia)了清空瀏覽(lan)器(qi)(qi)里sessionId在kc的(de)會話(hua)信息,但如果是httpclient的(de)調用,咱們(men)是拿不到客戶端瀏覽(lan)器(qi)(qi)的(de)cookie的(de)

  • Code '%s' already used for userSession
    org.keycloak.protocol.oidc.util.OAuth2CodeParser.parseCode這塊添(tian)加了clientId的日志描述(shu)

圖片

修改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()
  • 添加了代碼邏輯,實現了按需自動執行
     //對已有用戶進行更新,注意,可能會覆蓋用戶的其它屬性
    FederatedIdentityModel finalFederatedIdentityModel = federatedIdentityModel;
    sessionFactory.getProviderFactoriesStream(IdentityProviderMapper.class)
        .map(IdentityProviderMapper.class::cast)
        .map(mapper -> Arrays.stream(mapper.getCompatibleProviders())
            .filter(type -> Objects.equals(finalFederatedIdentityModel.getIdentityProvider(), type))
            .map(type -> mapper)
            .findFirst()
            .orElse(null))
        .filter(Objects::nonNull)
        .collect(Collectors.toMap(IdentityProviderMapper::getId, Function.identity()))
        .forEach((a, b) -> {
          IdentityProviderMapper target = (IdentityProviderMapper) sessionFactory
              .getProviderFactory(IdentityProviderMapper.class, a);
          IdentityProviderMapperModel identityProviderMapperModel = new IdentityProviderMapperModel();
          identityProviderMapperModel.setConfig(new HashMap<>());
          identityProviderMapperModel.setSyncMode(IdentityProviderMapperSyncMode.FORCE);
          identityProviderMapperModel.setId(a);
          identityProviderMapperModel.setIdentityProviderMapper(finalFederatedIdentityModel.getIdentityProvider());
          identityProviderMapperModel.setIdentityProviderAlias(finalFederatedIdentityModel.getIdentityProvider());
          try {
            if (!Objects.equals(target.getId(), UsernameTemplateMapper.PROVIDER_ID)) {
              IdentityProviderMapperSyncModeDelegate.delegateUpdateBrokeredUser(session, realmModel, federatedUser,
                  identityProviderMapperModel,
                  context, target);
            }

          } catch (RuntimeException ex) {

          }

        });
  • 添加一個例子,實現社區登錄的類型自動存儲到用戶屬性loginType中,getCompatibleProviders()方法中綁定了
    IdentityProviderMapper.ANY_PROVIDER,所以在每個社區登錄后,它都會被執行
  • 新用戶不會綁定這個,已綁定的用戶才執行這個方法,原因是syncModel為Force,表示當有用戶后,會強制更新它
public class V6UserAttributeMapper extends AbstractJsonUserAttributeMapper {
  public static final String PROVIDER_ID = "v6-user-attribute-mapper";
  private static final String[] cp = new String[] {IdentityProviderMapper.ANY_PROVIDER};

  @Override
  public String[] getCompatibleProviders() {
    return cp;
  }

  @Override
  public String getId() {
    return PROVIDER_ID;
  }

  @Override
  public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user,
                                 IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
    logger.info("updateBrokeredUser user info...");

    user.setSingleAttribute("loginType", mapperModel.getIdentityProviderAlias());

  }
}
  • 登錄后更新用戶屬性loginType

圖片

登錄后,將loginType添加到refresh_token中

解決由(you)于(yu)kc的(de)refresh_token不(bu)支持自定義屬性,所以(yi)在登錄后,將loginType添加到(dao)refresh_token中(zhong),這樣在refresh_token時,就可以(yi)獲取到(dao)loginType了(le)

  • 實現邏輯:將當前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里
Cookie cookie = CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) {
  cookie =
      CookieHelper.getCookie(headers.getCookies(), KEYCLOAK_IDENTITY_COOKIE + CookieHelper.LEGACY_COOKIE);
  if (cookie == null) {
    AuthenticationManager.createLoginCookie(session, realm, user, userSession, session.getContext().getUri(),
        session.getContext().getConnection());
  }
}
  • 關于對loginType和登錄事件的修改
    請查看TODO: 20230406的注釋代碼
  • 涉及到以下動作會觸發的事件,會添加我們擴展的屬性
    • 共享登錄
    • code換token
    • 刷新token

關于OTP提供商的調研

  • OTP提供商的策略:org.keycloak.models.OTPPolicy,目前支持FreeOTP和GoogleAuthenticator

圖片

關于keycloak-services項目添加第三方jar包的問題

我們例如將org.infinispan這(zhe)個(ge)包,在kc里(li)也(ye)是一個(ge)module,引用到(dao)keycloak-services項目,它(ta)在啟動時會報錯,告訴(su)找不(bu)到(dao)這(zhe)個(ge)org.infinispan.Cache類,類似這(zhe)種(zhong)類無法找到(dao)的錯誤。

Uncaught server error: java.lang.NoClassDefFoundError: org/infinispan/Cache
        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節點下添加依賴,如
<module name="org.infinispan"/>

  1. 修改Dockerfile文件,將這個module.xml文件也復制到上面的容器目錄,覆蓋原來的文件
  2. 重新構建鏡像,啟動容器,問題解決

自動登錄接口同一瀏覽器添加踢出之前登錄的邏輯

  • 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 需(xu)要添(tian)加(jia)邏輯,去檢索(suo)事件(jian)中是否包括(kuo)了權限變更的用戶

  • 驗證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

用戶權限更新后,通和邏輯整理

  1. kc服務端收到REALM_ROLE_MAPPING或者USER_ROLE_CHANGE事件后,向infinispan緩存里添加一個key,key是
    xxx_user_modify_role_{userId},value是空
  2. 它有緩存有效期與access_token的相同,目前為30分鐘
  3. 當用戶進行code換token或者刷新token時,根據當前用戶id,去上面緩存中找,如果查找到,說明這個用戶的權限發生了變更
  4. 找到后,向這個緩存xxx_user_modify_role_{userId}添加value,value格式是{sessionId}_{clientId},就是用戶在哪個瀏覽器
    哪個客戶端訪問
  5. 當用戶調用驗證token接口時,如果在xxx_user_modify_role_{userId}中沒有找到這個value{sessionId}_{clientId},就驗證失敗
  6. 當驗證失敗后,返回401,用戶再去刷新token,向xxx_user_modify_role_{userId}中添加對應的value, 保持下次驗證會成功

獲取IP地址的方法修改

// TODO: 優(you)化登錄(lu)事件中,獲(huo)取ipAddress的邏輯(ji),改為(wei)real-ip有限(xian)

  • 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秒
  public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
    if (userSession == null) {
        logger.debug("No user session");
        return false;
    }
    int currentTime = Time.currentTime();

    // Additional time window is added for the case when session was updated in different DC and the update to current DC was postponed
    int maxIdle = userSession.isRememberMe() && realm.getSsoSessionIdleTimeoutRememberMe() > 0 ?
            realm.getSsoSessionIdleTimeoutRememberMe() : realm.getSsoSessionIdleTimeout();
    int maxLifespan = userSession.isRememberMe() && realm.getSsoSessionMaxLifespanRememberMe() > 0 ?
            realm.getSsoSessionMaxLifespanRememberMe() : realm.getSsoSessionMaxLifespan();

    boolean sessionIdleOk =
            maxIdle > currentTime - userSession.getLastSessionRefresh() - SessionTimeoutHelper.IDLE_TIMEOUT_WINDOW_SECONDS;
    boolean sessionMaxOk = maxLifespan > currentTime - userSession.getStarted();
    return sessionIdleOk && sessionMaxOk;
}

session idle和session max的邏輯生效,如果修改refresh_token生成時的校驗邏輯

org.keycloak.protocol.oidc
.TokenManager.refreshAccessToken()方法中的代碼,將verifyRefreshToken方法參數中的checkExpiration改成false
// TODO: 完善實現了在線校驗時,session idle和session max的功能

session idle(空閑過期時間)和session max(最大過期時間)不相等時,產生的問題

描述與解決思路

  1. session idle會作為刷新token的過期時間
  2. 當這個時間到達后,不能再刷新token了,但是,session還是在線的
  3. 是否需要在到達這個時間后,將會話刪除?
  4. 如果真要刪除的話,可能產生的問題就是session max的時間還沒到,但是session已經被刪除了,這樣就會導致session max的時間不準確了
  5. 但如果session idle到達,并且token沒有成功刷新,這說明用戶空閑了,這時session是可以刪除的,與4不矛盾
  6. 解決方法
    *[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時間內是否有操作有關

生成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

MultivaluedMap<String, String> query = session.getContext().getUri().getQueryParameters();
if(query.

containsKey("theme")){
name=query.

getFirst("theme");
}else{
        }

登錄跨域支持

org.keycloak.protocol.oidc.endpoints.TokenEndpoint.resourceOwnerPasswordCredentialsGrant(),返(fan)回(hui)值添(tian)加跨域代碼

return cors.builder(Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).

allowAllOrigins().

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()方法
    //TODO:刷新token時,如果用戶有required action,拋出異常
    if (user.getRequiredActionsStream().findAny().isPresent()) {
      throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User has required action",
          "User has required action");
    }

社區登錄state的自定義

社區登錄回調state參數,支持4個參數

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

圖片

建立社區登錄地址時添加自定義state參數

  • AbstractOAuth2IdentityProvider類中createAuthorizationUrl方法,修改state參數的拼接
 String state = request.getState().getEncoded();
    if(request.

getAuthenticationSession().

getAuthNote("g") !=null&&
        request.

getAuthenticationSession().

getAuthNote("g").

trim() !=""){
state =state +"."+request.

getAuthenticationSession().

getAuthNote("g");
    }

圖片

在認證成功后federatedIdentityContext上下文添加參數

  • AbstractOAuth2IdentityProvider類中Endpoint.authResponse方法,再返回之前為federatedIdentity添加groupId參數
// 添加集團代碼
String[] decoded = DOT.split(state, 4);
if(decoded.length ==4){
        federatedIdentity.

setUserAttribute("groupId",decoded[3]);
}

圖片

登錄超時的提示語調整

  • 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
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
catch (OAuthErrorException e) {
      logger.trace(e.getMessage(), e);
      // KEYCLOAK-6771 Certificate Bound Token
      if (MtlsHoKTokenUtil.CERT_VERIFY_ERROR_DESC.equals(e.getDescription())) {
        event.error(Errors.NOT_ALLOWED);
        throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.UNAUTHORIZED);
      } else {
        event.error(Errors.INVALID_TOKEN);
        throw new CorsErrorResponseException(cors, e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
      }
 }

社區登錄添加loginType為社區的idp

  • 情況一,未綁定用戶,走first flow
  • 情況二,綁定了用戶,下次再登錄,會走after flow

first flow

org.keycloak.services.resources.IdentityBrokerService.authenticated()方法,添加代碼

authenticationSession.setUserSessionNote("loginType",context.getIdpConfig().getProviderId());

圖片

after flow

org.keycloak.services.resources.IdentityBrokerService.authenticated()方法,添加代碼

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<String, String> attributes, Integer firstResult, Integer maxResults)方法

個人中心綁定社區用戶

代(dai)碼(ma)注(zhu)(zhu)釋,去掉(diao)權限(xian)的控制(zhi):IdentityBrokerService.clientInitiatedAccountLinking()方法(fa),注(zhu)(zhu)冊下面代(dai)碼(ma)

  • 出錯信息:not_allowed
//      if (!userAccountRoles.contains(manageAccountRole)) {
//        RoleModel linkRole = accountService.getRole(AccountRoles.MANAGE_ACCOUNT_LINKS);
//        if (!userAccountRoles.contains(linkRole)) {
//          event.error(Errors.NOT_ALLOWED);
//          UriBuilder builder = UriBuilder.fromUri(redirectUri)
//              .queryParam(errorParam, Errors.NOT_ALLOWED)
//              .queryParam("nonce", nonce);
//          return Response.status(302).location(builder.build()).build();
//        }
//      }
  • 出錯信息:insufficientPermissionMessage
//    if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)
//        .getRole(AccountRoles.MANAGE_ACCOUNT))) {
//      return redirectToErrorPage(authSession, Response.Status.FORBIDDEN, Messages.INSUFFICIENT_PERMISSION);
//    }
  • 修改非account客戶端的錯誤頁邏輯,直接將錯誤編碼帶著來源頁
  • IdentityBrokerService.redirectToErrorWhenLinkingFailed()
 private Response redirectToErrorWhenLinkingFailed(AuthenticationSessionModel authSession, String message,
                                                   Object... parameters) {
    if (authSession.getClient() != null &&
            authSession.getClient().getClientId().equals(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)) {
        return redirectToAccountErrorPage(authSession, message, parameters);
    } else {
        //  return redirectToErrorPage(authSession, Response.Status.BAD_REQUEST, message, parameters); // Should rather redirect to app instead and display error here?
        // 當出現錯誤,將錯誤消息直接帶到來源頁
        URI errUrl =
                UriBuilder.fromUri(authSession.getRedirectUri()).queryParam("error", message).build();
        return Response.status(302).location(errUrl).build();

    }
}

首次登錄社區,并完成老用戶的綁定,向FEDERATED_IDENTITY_LINK事件添加corpId

  • org.keycloak.services.resources.IdentityBrokerService.afterFirstBrokerLogin(AuthenticationSessionModel authSession)
    方法中添加代碼
event.detail(CORP_ID,context.getUserAttribute(CORP_ID)); // 這塊與認證頁有跨頁,所以authSession.getAuthNote(CORP_ID)無法獲取到corpId,所以臨時存在userAttribute對應的內存中,并存持久化到數據庫
  • 具體的AbstractOAuth2IdentityProvider子類中Endpoint.authResponse()方法中添加代碼
//11.3.0之后改成這樣了,去掉了code字段
federatedIdentity.setUserAttribute("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));
// authSession.setAuthNote("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));

已登錄的用戶去綁定社區用戶時,向FEDERATED_IDENTITY_LINK事件添加corpId

  • org.keycloak.services.resources.IdentityBrokerService.performAccountLinking()方法中添加代碼
  • 這塊為了統一,也使用getUserAttribute即可
this.event.user(authenticatedUser)
    .detail(Details.USERNAME, authenticatedUser.getUsername())
    .detail(Details.IDENTITY_PROVIDER, newModel.getIdentityProvider())
    .detail(Details.IDENTITY_PROVIDER_USERNAME, newModel.getUserName())
    .detail(CORP_ID,federatedIdentity.getUserAttribute(CORP_ID))// 從已經登錄的用戶點社區登錄,綁定事件中添加corpId
    .success();

parseSessionCode報錯

AuthenticationSessionManager.getCurrentAuthenticationSession authSessionCookies這塊添加日志,看是否kc可以(yi)獲取(qu)到瀏(liu)覽(lan)器cookie中的(de)auth_session_id,如果(guo)獲取(qu)不(bu)到會出現(xian)下(xia)面錯誤(wu)

ERROR [org.keycloak.services.resources.IdentityBrokerService] (default task-1709) unexpectedErrorHandlingRequestMessage: javax.ws.rs.WebApplicationException: HTTP 400 Bad Request
	at org.keycloak.keycloak-services@14.0.0//org.keycloak.services.resources.IdentityBrokerService.parseSessionCode(IdentityBrokerService.java:1225)
	at org.keycloak.keycloak-services@14.0.0//org.keycloak.services.resources.IdentityBrokerService.performLogin(IdentityBrokerService.java:419)
	at jdk.internal.reflect.GeneratedMethodAccessor673.invoke(Unknown Source)
	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,包含最基礎的會話信息
eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTFiNDI0OC02OGZjLTQwNDQtYjM4Ny1kMGNjOTI3ZWI1MmIifQ.eyJleHAiOjE3NjE5MTY2OTgsImlhdCI6MTc2MTg4MDY5OCwianRpIjoiYTIwNDFjNTgtZmE5NC00MDA4LTg3YzEtZTI1MWEwMmZmNjk2IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNC4yNjo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjZlMjZjNGQwLTZiMzktNDllYy1hNWE0LWI3MzBkOTA3ZjM3ZiIsInR5cCI6IlNlcmlhbGl6ZWQtSUQiLCJzZXNzaW9uX3N0YXRlIjoiOGI5YjgyMDUtMTcyYi00YzFiLWFmNzYtNGI1Yjk4ZTE4YzY4Iiwic3RhdGVfY2hlY2tlciI6InNGdDkxOFBWcnFDaGxWNV8wYm5RY0pxZVJ2dlYyS3hQbU9lRTBfV3dPRjQifQ.KRViHyjY54UhswmXnCCMpSRY9SoV2k3yANXfUtQpLvc

{
    "exp": 1761916698,
    "iat": 1761880698,
    "jti": "a2041c58-fa94-4008-87c1-e251a02ff696",
    "iss": "//192.168.4.26:8080/auth/realms/master",
    "sub": "6e26c4d0-6b39-49ec-a5a4-b730d907f37f",
    "typ": "Serialized-ID",
    "session_state": "8b9b8205-172b-4c1b-af76-4b5b98e18c68",
    "state_checker": "sFt918PVrqChlV5_0bnQcJqeRvvV2KxPmOeE0_WwOF4"
}
  • 生成一個auth_session_id到瀏覽器cookie中
  • IdentityBrokerService.clientInitiatedAccountLinking()方法中,調用
    AuthenticationSessionManager.getCurrentAuthenticationSession()方法,解析瀏覽器cookie中的auth_session_id
AuthenticationManager.AuthResult cookieResult =
        AuthenticationManager.authenticateIdentityCookie(session, realmModel, true);
//...
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);

// Refresh the cookie
new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);

img_2.png

  • AuthenticationManager類中的authenticateIdentityCookie用來生成一個AuthResult對象,如果用戶已經登錄(有KEYCLOAK_IDENTITY_COOKIE)這個auth_session_id就會被使用
public static AuthResult authenticateIdentityCookie(KeycloakSession session, RealmModel realm, boolean checkActive) {
   Cookie cookie =
   CookieHelper.getCookie(session.getContext().getRequestHeaders().getCookies(), KEYCLOAK_IDENTITY_COOKIE);

   if (cookie == null || "".equals(cookie.getValue())) {
   logger.debugv("Could not find cookie: {0}", KEYCLOAK_IDENTITY_COOKIE);
   return null;
   }
   String tokenString = cookie.getValue();
   AuthResult authResult =
   verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(),
   checkActive, false, null, true, tokenString, session.getContext().getRequestHeaders(),
   VALIDATE_IDENTITY_COOKIE);
   if (authResult == null) {
   expireIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
   expireOldIdentityCookie(realm, session.getContext().getUri(), session.getContext().getConnection());
   return null;
   }
   authResult.getSession().setLastSessionRefresh(Time.currentTime());
   return authResult;
   }
  • AuthenticationSessionManager文件

    /**
     * @param authSessionId decoded authSessionId (without route info attached)
     * @param realm
     */
    public void setAuthSessionCookie(String authSessionId, RealmModel realm) {
        UriInfo uriInfo = session.getContext().getUri();
        String cookiePath = AuthenticationManager.getRealmCookiePath(realm, uriInfo);

        boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());

        StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class);
        String encodedAuthSessionId = encoder.encodeSessionId(authSessionId);

        CookieHelper.addCookie(AUTH_SESSION_ID, encodedAuthSessionId, cookiePath, null, null, -1, sslRequired, true, SameSiteAttributeValue.NONE);

        log.debugf("Set AUTH_SESSION_ID cookie with value %s", encodedAuthSessionId);
    }
    /**
     *
     * @param encodedAuthSessionId encoded ID with attached route in cluster environment (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1" )
     * @return object with decoded and actually encoded authSessionId
     */
    AuthSessionId decodeAuthSessionId(String encodedAuthSessionId) {
        log.debugf("Found AUTH_SESSION_ID cookie with value %s", encodedAuthSessionId);
        StickySessionEncoderProvider encoder = session.getProvider(StickySessionEncoderProvider.class);
        String decodedAuthSessionId = encoder.decodeSessionId(encodedAuthSessionId);
        String reencoded = encoder.encodeSessionId(decodedAuthSessionId);

        return new AuthSessionId(decodedAuthSessionId, reencoded);
    }

圖片

  • 我在獲取auth_session_id的代碼段添加日志后,發現在跨域iframe對接kc時,kc服務端無法獲取到auth_session_id,所以最后導致出現parseSessionCode

圖片

posted @ 2025-10-31 13:20  張占嶺  閱讀(4)  評論(0)    收藏  舉報