
mvn package -Denforcer.skip=true -Dmaven.test.skip=true
mvn clean install -Dskip=true
$ mvn deploy -Denforcer.skip=true -Dmaven.test.skip=true
org.keycloak.services.resources.LoginActionsServiceChecks// TODO: 当用户已经登录了,直接跳到首页
Response.status(302).header(HttpHeaders.LOCATION, "https://www.xxx.com").build();



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


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

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

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




1 code的生产

2 code的校验

//对已有用户进行更新,注意,可能会覆盖用户的其它属性
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) {
}
});
IdentityProviderMapper.ANY_PROVIDER,所以在每个社区登录后,它都会被执行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());
}
}

解决由于kc的refresh_token不支持自定义属性,所以在登录后,将loginType添加到refresh_token中,这样在refresh_token时,就可以获取到loginType了
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());
}
}

我们例如将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节点下添加依赖,如


// TODO: xxx_user_modify_role 需要添加逻辑,去检索事件中是否包括了权限变更的用户
用户权限更新后,通和逻辑整理
REALM_ROLE_MAPPING或者USER_ROLE_CHANGE事件后,向infinispan缓存里添加一个key,key是xxx_user_modify_role_{userId},value是空xxx_user_modify_role_{userId}添加value,value格式是{sessionId}_{clientId},就是用户在哪个浏览器客户端访问验证token接口时,如果在xxx_user_modify_role_{userId}中没有找到这个value{sessionId}_{clientId},就验证失败xxx_user_modify_role_{userId}中添加对应的value, 保持下次验证会成功// TODO: 优化登录事件中,获取ipAddress的逻辑,改为real-ip有限
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;
}
org.keycloak.protocol.oidc
.TokenManager.refreshAccessToken()方法中的代码,将verifyRefreshToken方法参数中的checkExpiration改成false
// TODO: 完善实现了在线校验时,session idle和session max的功能

session not active这样的错误。
MultivaluedMap 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();


//TODO:刷新token时,如果用户有required action,抛出异常
if (user.getRequiredActionsStream().findAny().isPresent()) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "User has required action",
"User has required action");
}
String[] decoded = DOT.split(encodedState, 4);从3改成4
String state = request.getState().getEncoded();
if(request.
getAuthenticationSession().
getAuthNote("g") !=null&&
request.
getAuthenticationSession().
getAuthNote("g").
trim() !=""){
state =state +"."+request.
getAuthenticationSession().
getAuthNote("g");
}

// 添加集团代码
String[] decoded = DOT.split(state, 4);
if(decoded.length ==4){
federatedIdentity.
setUserAttribute("groupId",decoded[3]);
}

allowedOrigins("*")claims.put("session_state", userSession.getId());// 添加当前的session信息
Session not active或者Invalid refresh tokenClient secret not provided in requestcatch (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);
}
}
org.keycloak.services.resources.IdentityBrokerService.authenticated()方法,添加代码
authenticationSession.setUserSessionNote("loginType",context.getIdpConfig().getProviderId());

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

user.setBrokerUserId(id);代码注释,去掉权限的控制: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();
// }
// }
// if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID)
// .getRole(AccountRoles.MANAGE_ACCOUNT))) {
// return redirectToErrorPage(authSession, Response.Status.FORBIDDEN, Messages.INSUFFICIENT_PERMISSION);
// }
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();
}
}
event.detail(CORP_ID,context.getUserAttribute(CORP_ID)); // 这块与认证页有跨页,所以authSession.getAuthNote(CORP_ID)无法获取到corpId,所以临时存在userAttribute对应的内存中,并存持久化到数据库
//11.3.0之后改成这样了,去掉了code字段
federatedIdentity.setUserAttribute("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));
// authSession.setAuthNote("corpId",federatedIdentity.getUserAttribute(DINGTALK_CORP_ID));
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();
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
5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1,这样在集群环境下,可以将请求路由到对应的节点上去。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"
}
AuthenticationSessionManager.getCurrentAuthenticationSession()方法,解析浏览器cookie中的auth_session_idAuthenticationManager.AuthResult cookieResult =
AuthenticationManager.authenticateIdentityCookie(session, realmModel, true);
//...
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
// Refresh the cookie
new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);

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;
}
/**
* @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);
}


登录查看全部
参与评论
手机查看
返回顶部