本地调试入口
编译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, "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()
- 添加了代码逻辑,实现了按需自动实行
- //对已有用户进行更新,注意,可能会覆盖用户的其它属性
- 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添加到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里
- 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的表明代码
- 涉及到以下动作会触发的事故,会添加我们扩展的属性
关于OTP提供商的调研
- OTP提供商的战略:org.keycloak.models.OTPPolicy,现在支持FreeOTP和GoogleAuthenticator
关于keycloak-services项目添加第三方jar包的标题
我们比方将org.infinispan这个包,在kc里也是一个module,引用到keycloak-services项目,它在启动时会报错,告诉找不到这个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中,添加对应的模块即可
从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秒
- 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(最大逾期时间)不相称时,产生的标题
形貌与办理思绪
- 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
- 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();
复制代码 登录回调所在中添加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 attributes, Integer firstResult, Integer maxResults)方法
个人中心绑定社区用户
代码表明,去掉权限的控制:IdentityBrokerService.clientInitiatedAccountLinking()方法,注册下面代码
- // 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可以获取到欣赏器cookie中的auth_session_id,如果获取不到会出现下面错误- 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": "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()方法中,调用
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);
复制代码
- 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
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |