<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      keycloak~keycloak14.0源代碼二次開發(fā)

      本地調(diào)試入口

      圖片

      編譯keycloak源代碼某個包

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

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

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

      圖片

      圖片

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

      圖片

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

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

      圖片

      生成token時添加日志

      圖片

      修改code to token的緩存類型

      默認(rèn)使用BasicCache,應(yīng)該是本地緩存,查通過查看TokenEndpoint,發(fā)現(xiàn)是分布式緩存

      圖片

      LOGIN事件的個性化配置

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

      圖片

      code_to_token時,去掉了clientId限制

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

      code_to_token時對瀏覽器sessionId操作

      • 當(dāng)code to token出現(xiàn)錯誤時,添加了清空瀏覽器里sessionId在kc的會話信息,但如果是httpclient的調(diào)用,咱們是拿不到客戶端瀏覽器的cookie的

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

      圖片

      修改kc管理后臺session列表由于信息不全報(bào)錯的問題

      • 主要是ModelToRepresentation報(bào)錯了,應(yīng)該是client_id為空引起的,像refresh_token達(dá)到次數(shù)會引起會話的client_id為空,但sessionId還是在線的。
      • 這塊將異常報(bào)錯復(fù)原了,如果不報(bào)錯,將會出現(xiàn)大量session從數(shù)據(jù)庫加載,導(dǎo)致數(shù)據(jù)庫崩盤

      圖片

      驗(yàn)證token去掉協(xié)議名的限制

      • 對在線token的校驗(yàn),去掉了https和http的校驗(yàn),只要后面域名相同就是ISS(Issuer)相同就行,這塊在驗(yàn)證token時會用到,另外在適配器集成中,每人請求加載前,也會用到它

      圖片

      圖片

      調(diào)查code碼被占用問題(生產(chǎn)了重復(fù)的code碼)

      1 code的生產(chǎn)

      圖片

      2 code的校驗(yàn)

      圖片

      • 解決同時打開兩個登錄窗口,在第一個窗口登錄后,在第二個窗口再登錄一次,會出現(xiàn)“您已經(jīng)登錄”的頁面
      • 解決:發(fā)生上面的情況后,直接跳到v6首頁
        • LoginActionsServiceChecks.checkNotLoggedInYet()方法
        • SessionCodeChecks.initialVerifyAuthSession()方法

      讓IdentityProviderMapper實(shí)現(xiàn)的類型,自動為社區(qū)登錄執(zhí)行delegateUpdateBrokeredUser

      • 修改源代碼:org.keycloak.services.resources.updateFederatedIdentity()
      • 添加了代碼邏輯,實(shí)現(xiàn)了按需自動執(zhí)行
           //對已有用戶進(jìn)行更新,注意,可能會覆蓋用戶的其它屬性
          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) {
      
                }
      
              });
      
      • 添加一個例子,實(shí)現(xiàn)社區(qū)登錄的類型自動存儲到用戶屬性loginType中,getCompatibleProviders()方法中綁定了
        IdentityProviderMapper.ANY_PROVIDER,所以在每個社區(qū)登錄后,它都會被執(zhí)行
      • 新用戶不會綁定這個,已綁定的用戶才執(zhí)行這個方法,原因是syncModel為Force,表示當(dāng)有用戶后,會強(qiáng)制更新它
      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中

      解決由于kc的refresh_token不支持自定義屬性,所以在登錄后,將loginType添加到refresh_token中,這樣在refresh_token時,就可以獲取到loginType了

      • 實(shí)現(xiàn)邏輯:將當(dāng)前l(fā)oginType添加到當(dāng)前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()

      解決用戶瀏覽器因?yàn)閬G失keycloak_identity而keycloak_session_id有并且是在線的,導(dǎo)致無法登錄的問題

      • 在方法AuthorizationEndpointBase.createAuthenticationSession()添加了判斷邏輯,沒有keycloak_identity就重新根據(jù)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());
        }
      }
      
      • 關(guān)于對loginType和登錄事件的修改
        請查看TODO: 20230406的注釋代碼
      • 涉及到以下動作會觸發(fā)的事件,會添加我們擴(kuò)展的屬性
        • 共享登錄
        • code換token
        • 刷新token

      關(guān)于OTP提供商的調(diào)研

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

      圖片

      關(guān)于keycloak-services項(xiàng)目添加第三方j(luò)ar包的問題

      我們例如將org.infinispan這個包,在kc里也是一個module,引用到keycloak-services項(xiàng)目,它在啟動時會報(bào)錯,告訴找不到這個org.infinispan.Cache類,類似這種類無法找到的錯誤。

      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中,添加對應(yīng)的模塊即可

      從keycloak容器里將/opt/jboss/keycloak/modules/system/layers/keycloak/org/keycloak/keycloak-core/main/module.xml復(fù)制出來,在文件的dependencies節(jié)點(diǎn)下添加依賴,如
      <module name="org.infinispan"/>

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

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

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

      圖片

      從carsi網(wǎng)站過來的用戶,會帶有carsi-auto這個關(guān)鍵字,也應(yīng)該要踢出之前的登錄

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

      圖片

      刷新token和通過code換token邏輯中,添加infinispan緩存的邏輯,鍵key是sessionId+clientId,作用是當(dāng)用戶的角色變更后,用戶校驗(yàn)token直接返回false,讓迫使用戶重新去刷新token

      // TODO: xxx_user_modify_role 需要添加邏輯,去檢索事件中是否包括了權(quán)限變更的用戶

      • 驗(yàn)證token: org.keycloak.protocol.oidc.TokenManager.checkTokenValidForIntrospection()
      • 刷新token: org.keycloak.protocol.oidc.endpoints.TokenEndpoint.refreshTokenGrant
        ,這塊因?yàn)橄嗤膃vent對象,所以代碼遷移到keycloak-services-event-kafka
      • code換token: org.keycloak.protocol.oidc.endpoints.TokenEndpoint.codeToToken()
        ,這塊因?yàn)橄嗤膃vent對象,所以代碼遷移到keycloak-services-event-kafka

      用戶權(quán)限更新后,通和邏輯整理

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

      獲取IP地址的方法修改

      // TODO: 優(yōu)化登錄事件中,獲取ipAddress的邏輯,改為real-ip有限

      • org.keycloak.events.EventBuilder.ipAddress()進(jìn)行了重新賦值
      • org.keycloak.services.resources.admin.AdminEventBuilder.AdminEventBuilder()初始化時,使用real-ip

      驗(yàn)證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生成時的校驗(yàn)邏輯

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

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

      描述與解決思路

      1. session idle會作為刷新token的過期時間
      2. 當(dāng)這個時間到達(dá)后,不能再刷新token了,但是,session還是在線的
      3. 是否需要在到達(dá)這個時間后,將會話刪除?
      4. 如果真要刪除的話,可能產(chǎn)生的問題就是session max的時間還沒到,但是session已經(jīng)被刪除了,這樣就會導(dǎo)致session max的時間不準(zhǔn)確了
      5. 但如果session idle到達(dá),并且token沒有成功刷新,這說明用戶空閑了,這時session是可以刪除的,與4不矛盾
      6. 解決方法
        *[x] 在session idle到達(dá)后,將session刪除,應(yīng)該就解決問題了
        *[x] 或者在生成code之前,判斷它的session idle是否到期,如果到期,將會話刪除,不能生成code

      用戶會話過期,清理用戶會話的邏輯調(diào)整

      圖片

      • org.keycloak.services.scheduled.ScheduledTaskRunner # 默認(rèn)900秒(15分鐘)執(zhí)行一次
        • org.keycloak.services.scheduled.ClearExpiredUserSessionsTask
          • org.keycloak.models.map.authSession.removeExpired
          • 需要添加空閑過期時間的判斷,如果到期,就刪除會話
          • 這塊需要看如何手動清除,因?yàn)槟J(rèn)的,infinispan中的session,有自己的過期時間,按著過期時間自動清除的
          • 咱們相當(dāng)于,如何讓它按著咱們的時間(這個時間經(jīng)過了session idle的時間)來清除的
      • 通過上面的分析,直接從infinispan中獲取過期的session,并刪除不太可能,人家通知初始化的過期時間自行維護(hù)的,而且這種過期時間,是session
        max,而咱們的過期時間是可變的,它可能是一個session idle,也可能是session max,這與用戶是否在idle時間內(nèi)是否有操作有關(guān)

      生成code時,添加session idle的判斷

      • 如果不添加這個判斷,將會出現(xiàn)的問題是,當(dāng)session idle和session max設(shè)置不同時,當(dāng)session
        idle到期后,用戶的會話不會被刪除,導(dǎo)致刷新token和申請code碼換token,兩塊產(chǎn)生的token邏輯不一樣,最終效果就是,code可以換回來token,但token在校驗(yàn)時是
        session not active這樣的錯誤。
      • org.keycloak.protocol.AuthorizationEndpointBase.createAuthenticationSession()方法

      圖片

      修改org.keycloak.theme.DefaultThemeSelectorProvider文件getThemeName()方法,添加了url中皮膚參數(shù)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(),返回值添加跨域代碼

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

      登錄回調(diào)地址中添加loginType這個參數(shù)

      • org.keycloak.services.resources.IdentityBrokerService.finishBrokerAuthentication()方法添加對loginType的操作
      • org.keycloak.protocol.oidc.OIDCLoginProtocol.authenticated()方法中,獲取loginType,并添加到回調(diào)路徑的URL參數(shù)中

      圖片

      圖片

      社區(qū)登錄成功后,綁定用戶信息,修改FEDERATED_IDENTITY_LINK的內(nèi)容

      • 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");
          }
      

      社區(qū)登錄state的自定義

      社區(qū)登錄回調(diào)state參數(shù),支持4個參數(shù)

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

      圖片

      建立社區(qū)登錄地址時添加自定義state參數(shù)

      • AbstractOAuth2IdentityProvider類中createAuthorizationUrl方法,修改state參數(shù)的拼接
       String state = request.getState().getEncoded();
          if(request.
      
      getAuthenticationSession().
      
      getAuthNote("g") !=null&&
              request.
      
      getAuthenticationSession().
      
      getAuthNote("g").
      
      trim() !=""){
      state =state +"."+request.
      
      getAuthenticationSession().
      
      getAuthNote("g");
          }
      

      圖片

      在認(rèn)證成功后federatedIdentityContext上下文添加參數(shù)

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

      圖片

      登錄超時的提示語調(diào)整

      • keycloak-themes/themes/base/login/messages/messages_en.properties文件
      • 修改loginTimeout的值即可

      社區(qū)登錄頁{provider}/login頁添加idp和login_type參數(shù)

      • org.keycloak.services.Urls類identityProviderAuthnRequest()方法,添加idp參數(shù)的追加
      • org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticator類中redirect()
        方法構(gòu)建登錄頁重定向參數(shù),添加loginType和idp兩個參數(shù)

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

      • org.keycloak.protocol.oidc.OIDCLoginProtocolService類中的issueUserInfo()方法
      • 添加跨域支持allowedOrigins("*")
      • 解析當(dāng)前token,并添加session_state
      claims.put("session_state", userSession.getId());// 添加當(dāng)前的session信息
      

      刷新token時,出現(xiàn)Session not active或者Invalid refresh token

      • Session not active 表示用戶的session已經(jīng)過期了,需要重新登錄,返回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驗(yàn)證不通過,會走這個catch邏輯,大多數(shù)情況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);
            }
       }
      

      社區(qū)登錄添加loginType為社區(qū)的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());
      

      圖片

      社區(qū)登錄microsoft修改FEDERATED_IDENTITY_LINK的BUG

      • 用戶第一次使用社區(qū)來綁定本地KC用戶時,需要為社區(qū)用戶的unionId賦值到BrokeredIdentityContext對象
      • 在MicrosoftIdentityProvider.extractIdentityFromProfile()方法,添加了 user.setBrokerUserId(id);
      • 如果其它社區(qū)登錄需要集成,也需要手動添加上面的代碼
      • IdentityBrokerService.afterFirstBrokerLogin()方法,添加用戶第一次綁定社區(qū)時FEDERATED_IDENTITY_LINK的擴(kuò)展信息

      管理后臺-用戶檢索-改為用戶名精確或者郵箱精確

      • org.keycloak.models.jpa.JapUserProvider類
      • searchForUserStream(RealmModel realm, Map<String, String> attributes, Integer firstResult, Integer maxResults)方法

      個人中心綁定社區(qū)用戶

      代碼注釋,去掉權(quán)限的控制:IdentityBrokerService.clientInitiatedAccountLinking()方法,注冊下面代碼

      • 出錯信息: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?
              // 當(dāng)出現(xiàn)錯誤,將錯誤消息直接帶到來源頁
              URI errUrl =
                      UriBuilder.fromUri(authSession.getRedirectUri()).queryParam("error", message).build();
              return Response.status(302).location(errUrl).build();
      
          }
      }
      

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

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

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

      • org.keycloak.services.resources.IdentityBrokerService.performAccountLinking()方法中添加代碼
      • 這塊為了統(tǒng)一,也使用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))// 從已經(jīng)登錄的用戶點(diǎn)社區(qū)登錄,綁定事件中添加corpId
          .success();
      

      parseSessionCode報(bào)錯

      AuthenticationSessionManager.getCurrentAuthenticationSession authSessionCookies這塊添加日志,看是否kc可以獲取到瀏覽器cookie中的auth_session_id,如果獲取不到會出現(xiàn)下面錯誤

      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集群節(jié)點(diǎn)的標(biāo)識符,它們之間用點(diǎn)號分隔開,比如
        5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1,這樣在集群環(huán)境下,可以將請求路由到對應(yīng)的節(jié)點(diǎn)上去。
      • KEYCLOAK_IDENTITY它是用戶登錄之后產(chǎn)生的,它是一個jwt的token,包含最基礎(chǔ)的會話信息
      eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyMTFiNDI0OC02OGZjLTQwNDQtYjM4Ny1kMGNjOTI3ZWI1MmIifQ.eyJleHAiOjE3NjE5MTY2OTgsImlhdCI6MTc2MTg4MDY5OCwianRpIjoiYTIwNDFjNTgtZmE5NC00MDA4LTg3YzEtZTI1MWEwMmZmNjk2IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNC4yNjo4MDgwL2F1dGgvcmVhbG1zL21hc3RlciIsInN1YiI6IjZlMjZjNGQwLTZiMzktNDllYy1hNWE0LWI3MzBkOTA3ZjM3ZiIsInR5cCI6IlNlcmlhbGl6ZWQtSUQiLCJzZXNzaW9uX3N0YXRlIjoiOGI5YjgyMDUtMTcyYi00YzFiLWFmNzYtNGI1Yjk4ZTE4YzY4Iiwic3RhdGVfY2hlY2tlciI6InNGdDkxOFBWcnFDaGxWNV8wYm5RY0pxZVJ2dlYyS3hQbU9lRTBfV3dPRjQifQ.KRViHyjY54UhswmXnCCMpSRY9SoV2k3yANXfUtQpLvc
      
      {
          "exp": 1761916698,
          "iat": 1761880698,
          "jti": "a2041c58-fa94-4008-87c1-e251a02ff696",
          "iss": "http://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()方法中,調(diào)用
        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對象,如果用戶已經(jīng)登錄(有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的代碼段添加日志后,發(fā)現(xiàn)在跨域iframe對接kc時,kc服務(wù)端無法獲取到auth_session_id,所以最后導(dǎo)致出現(xiàn)parseSessionCode

      圖片

      posted @ 2025-10-31 13:20  張占嶺  閱讀(4)  評論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 免费AV片在线观看网址| 婷婷综合久久中文字幕| 超碰人人模人人爽人人喊手机版 | 暖暖 免费 高清 日本 在线观看5| 伊人久久大香线蕉av五月天| 成人精品自拍视频免费看| 贵溪市| 麻豆国产传媒精品视频| 国产高清在线男人的天堂| 亚洲伊人久久综合影院| 日韩熟女熟妇久久精品综合| 亚洲丰满熟女一区二区v| 欧美日本精品一本二本三区| 国产在线视频一区二区三区| 国产成人精品亚洲一区二区| 女性高爱潮视频| 四虎精品视频永久免费| 亚洲精品尤物av在线网站| 秋霞人妻无码中文字幕| 丝袜美腿视频一区二区三区| 无码人妻丰满熟妇区毛片| 国产不卡一区二区在线视频| 国产亚洲精品第一综合另类无码无遮挡又大又爽又黄的视频 | 中文字幕乱码视频32| 国产激情av一区二区三区| 国产精品亚洲中文字幕| 久久国产精品成人影院| 国产精品一区二区三区性色| 国产精品无码不卡在线播放 | 色综合色综合久久综合频道| 国产中文字幕在线一区| 少妇人妻偷人免费观看| 成人国产av精品免费网| 亚洲国产综合自在线另类| 美女一区二区三区在线观看视频| 中文乱码人妻系列一区二区| 最新亚洲av日韩av二区| 欧美人与zoxxxx另类| 免费大黄网站在线观看| 精品人妻午夜福利一区二区| 波多野结衣av一区二区三区中文|