Refactor AuthenticationSessionManager

Closes #43825

Signed-off-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com>
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Co-authored-by: Pedro Ruivo <1492066+pruivo@users.noreply.github.com>
Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
Pedro Ruivo 2025-10-30 11:26:07 +00:00 committed by GitHub
parent be6a3814fb
commit 6317c02a27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 168 additions and 230 deletions

View File

@ -99,6 +99,11 @@ In a scenario where {project_name} acts as a broker and connects via OpenID Conn
This behavior is deprecated and will be removed in a future version of Keycloak.
=== `AuthenticationManager.AuthResult` is now a record
The inner class `AuthenticationManager.AuthResult` in the `keycloak-services` module is now a record.
The getter methods like `getSession()` have been deprecated in favor of the `session()` accessors.
// ------------------------ Removed features ------------------------ //
== Removed features

View File

@ -53,12 +53,12 @@ public class CookieAuthenticator implements Authenticator {
} else {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, authSession.getProtocol());
authSession.setAuthNote(Constants.LOA_MAP, authResult.getSession().getNote(Constants.LOA_MAP));
context.setUser(authResult.getUser());
authSession.setAuthNote(Constants.LOA_MAP, authResult.session().getNote(Constants.LOA_MAP));
context.setUser(authResult.user());
AcrStore acrStore = new AcrStore(context.getSession(), authSession);
// Cookie re-authentication is skipped if re-authentication is required
if (protocol.requireReauthentication(authResult.getSession(), authSession)) {
if (protocol.requireReauthentication(authResult.session(), authSession)) {
// Full re-authentication, so we start with no loa
acrStore.setLevelAuthenticatedToCurrentRequest(Constants.NO_LOA);
authSession.setAuthNote(AuthenticationManager.FORCED_REAUTHENTICATION, "true");
@ -69,7 +69,7 @@ public class CookieAuthenticator implements Authenticator {
} else {
String topLevelFlowId = context.getTopLevelFlow().getId();
int previouslyAuthenticatedLevel = acrStore.getHighestAuthenticatedLevelFromPreviousAuthentication(topLevelFlowId);
AuthenticatorUtils.updateCompletedExecutions(context.getAuthenticationSession(), authResult.getSession(), context.getExecution().getId());
AuthenticatorUtils.updateCompletedExecutions(context.getAuthenticationSession(), authResult.session(), context.getExecution().getId());
if (acrStore.getRequestedLevelOfAuthentication(context.getTopLevelFlow()) > previouslyAuthenticatedLevel) {
// Step-up authentication, we keep the loa from the existing user session.
@ -85,7 +85,7 @@ public class CookieAuthenticator implements Authenticator {
// Cookie only authentication
acrStore.setLevelAuthenticatedToCurrentRequest(previouslyAuthenticatedLevel);
authSession.setAuthNote(AuthenticationManager.SSO_AUTH, "true");
context.attachUserSession(authResult.getSession());
context.attachUserSession(authResult.session());
if (isOrganizationContext(context)) {
// if re-authenticating in the scope of an organization, an organization must be resolved prior to authenticating the user

View File

@ -79,8 +79,8 @@ public class ResetCredentialChooseUser implements Authenticator, AuthenticatorFa
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(), context.getRealm(), true);
//skip user choice if sso session exists
if (authResult != null) {
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, authResult.getUser().getUsername());
context.setUser(authResult.getUser());
context.getAuthenticationSession().setAuthNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME, authResult.user().getUsername());
context.setUser(authResult.user());
context.success();
return;
}

View File

@ -49,9 +49,9 @@ public class UserSessionLimitsAuthenticator implements Authenticator {
// check if new user and client session are needed
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(), context.getRealm(), true);
final boolean newUserSession = authResult == null || authResult.getSession() == null;
final boolean newClientSession = authResult == null || authResult.getSession() == null
|| authResult.getSession().getAuthenticatedClientSessionByClient(currentClient.getId()) == null;
final boolean newUserSession = authResult == null || authResult.session() == null;
final boolean newClientSession = authResult == null || authResult.session() == null
|| authResult.session().getAuthenticatedClientSessionByClient(currentClient.getId()) == null;
// Get the configuration for this authenticator
behavior = config.get(UserSessionLimitsAuthenticatorFactory.BEHAVIOR);

View File

@ -32,7 +32,7 @@ public class Tokens {
AuthResult authResult = new AppAuthManager.BearerTokenAuthenticator(keycloakSession).authenticate();
if (authResult != null) {
return authResult.getToken();
return authResult.token();
}
return null;
@ -44,7 +44,7 @@ public class Tokens {
.authenticate();
if (authResult != null) {
return authResult.getToken();
return authResult.token();
}
return null;

View File

@ -409,7 +409,7 @@ public class OID4VCIssuerEndpoint {
String vcIssuanceFlow = clientSession.getNote(PreAuthorizedCodeGrantType.VC_ISSUANCE_FLOW);
if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) {
AccessToken accessToken = bearerTokenAuthenticator.authenticate().getToken();
AccessToken accessToken = bearerTokenAuthenticator.authenticate().token();
if (Arrays.stream(accessToken.getScope().split(" "))
.noneMatch(tokenScope -> tokenScope.equals(requestedCredential.getScope()))) {
LOGGER.debugf("Scope check failure: required scope = %s, " +
@ -523,8 +523,8 @@ public class OID4VCIssuerEndpoint {
String mappingKey = CREDENTIAL_IDENTIFIER_PREFIX + credentialRequestVO.getCredentialIdentifier();
// First try to get the client session and look for the mapping there
UserSessionModel userSession = authResult.getSession();
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(authResult.getClient().getId());
UserSessionModel userSession = authResult.session();
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(authResult.client().getId());
String mappedCredentialConfigurationId = null;
if (clientSession != null) {
@ -947,11 +947,11 @@ public class OID4VCIssuerEndpoint {
private AuthenticatedClientSessionModel getAuthenticatedClientSession() {
AuthenticationManager.AuthResult authResult = getAuthResult();
UserSessionModel userSessionModel = authResult.getSession();
UserSessionModel userSessionModel = authResult.session();
AuthenticatedClientSessionModel clientSession = userSessionModel.
getAuthenticatedClientSessionByClient(
authResult.getClient().getId());
authResult.client().getId());
if (clientSession == null) {
throw new BadRequestException(getErrorResponse(ErrorType.INVALID_TOKEN));
}
@ -1126,16 +1126,16 @@ public class OID4VCIssuerEndpoint {
Map<String, Object> subjectClaims = new HashMap<>();
protocolMappers
.forEach(mapper -> mapper.setClaimsForSubject(subjectClaims, authResult.getSession()));
.forEach(mapper -> mapper.setClaimsForSubject(subjectClaims, authResult.session()));
// Validate that requested claims from authorization_details are present
validateRequestedClaimsArePresent(subjectClaims, authResult.getSession(), credentialConfig.getScope());
validateRequestedClaimsArePresent(subjectClaims, authResult.session(), credentialConfig.getScope());
// Include all available claims
subjectClaims.forEach((key, value) -> vc.getCredentialSubject().setClaims(key, value));
protocolMappers
.forEach(mapper -> mapper.setClaimsForCredential(vc, authResult.getSession()));
.forEach(mapper -> mapper.setClaimsForCredential(vc, authResult.session()));
LOGGER.debugf("The credential to sign is: %s", vc);

View File

@ -259,8 +259,8 @@ public class LogoutEndpoint {
// Check if we have session in the browser. If yes and it is different session than referenced by id_token_hint, the confirmation should be displayed
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
userSession = authResult.getSession();
if (idToken != null && idToken.getSessionState() != null && !idToken.getSessionState().equals(authResult.getSession().getId())) {
userSession = authResult.session();
if (idToken != null && idToken.getSessionState() != null && !idToken.getSessionState().equals(authResult.session().getId())) {
forcedConfirmation = true;
}
} else {
@ -440,7 +440,7 @@ public class LogoutEndpoint {
// authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) {
userSession = userSession != null ? userSession : authResult.getSession();
userSession = userSession != null ? userSession : authResult.session();
return initiateBrowserLogout(userSession);
} else if (userSession != null) {
// identity cookie is missing but there's valid id_token_hint which matches session cookie => continue with browser logout

View File

@ -130,9 +130,9 @@ public class StandardTokenExchangeProvider extends AbstractTokenExchangeProvider
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Invalid token", Response.Status.BAD_REQUEST);
}
UserModel tokenUser = authResult.getUser();
UserSessionModel tokenSession = authResult.getSession();
AccessToken token = authResult.getToken();
UserModel tokenUser = authResult.user();
UserSessionModel tokenSession = authResult.session();
AccessToken token = authResult.token();
event.user(tokenUser);
event.detail(Details.USERNAME, tokenUser.getUsername());

View File

@ -123,9 +123,9 @@ public class V1TokenExchangeProvider extends AbstractTokenExchangeProvider {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Invalid token", Response.Status.BAD_REQUEST);
}
tokenUser = authResult.getUser();
tokenSession = authResult.getSession();
token = authResult.getToken();
tokenUser = authResult.user();
tokenSession = authResult.session();
token = authResult.token();
}
String requestedSubject = context.getFormParams().getFirst(OAuth2Constants.REQUESTED_SUBJECT);

View File

@ -230,7 +230,7 @@ public class SamlService extends AuthorizationEndpointBase {
return error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST);
}
// assume this is a logout response
UserSessionModel userSession = authResult.getSession();
UserSessionModel userSession = authResult.session();
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.warn("Unknown saml response.");
logger.warn("UserSession is not tagged as logging out.");
@ -567,7 +567,7 @@ public class SamlService extends AuthorizationEndpointBase {
boolean postBinding = Objects.equals(SamlProtocol.SAML_POST_BINDING, logoutBinding);
String bindingUri = SamlProtocol.getLogoutServiceUrl(session, client, logoutBinding, false);
UserSessionModel userSession = authResult.getSession();
UserSessionModel userSession = authResult.session();
userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
if (samlClient.requiresRealmSignature()) {
userSession.setNote(SamlProtocol.SAML_LOGOUT_SIGNATURE_ALGORITHM, samlClient.getSignatureAlgorithm().toString());

View File

@ -49,8 +49,8 @@ public class AppAuthManager extends AuthenticationManager {
AuthResult authResult = super.authenticateIdentityCookie(session, realm);
if (authResult == null) return null;
// refresh the cookies!
createLoginCookie(session, realm, authResult.getUser(), authResult.getSession(), session.getContext().getUri(), session.getContext().getConnection());
if (authResult.getSession().isRememberMe()) createRememberMeCookie(authResult.getUser().getUsername(), session.getContext().getUri(), session);
createLoginCookie(session, realm, authResult.user(), authResult.session(), session.getContext().getUri(), session.getContext().getConnection());
if (authResult.session().isRememberMe()) createRememberMeCookie(authResult.user().getUsername(), session.getContext().getUri(), session);
return authResult;
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.managers;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
class AuthSessionId {
// Decoded ID of authenticationSession WITHOUT route attached (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7")
private final String decodedId;
// Encoded ID of authenticationSession WITH route attached (EG. "5e161e00-d426-4ea6-98e9-52eb9844e2d7.node1")
private final String encodedId;
AuthSessionId(String decodedId, String encodedId) {
this.decodedId = decodedId;
this.encodedId = encodedId;
}
public String getDecodedId() {
return decodedId;
}
public String getEncodedId() {
return encodedId;
}
}

View File

@ -913,11 +913,11 @@ public class AuthenticationManager {
AuthResult authResult = verifyIdentityToken(session, realm, session.getContext().getUri(), session.getContext().getConnection(), checkActive, false, null, true, tokenString,
session.getContext().getRequestHeaders(), verifier -> verifier.withChecks(VALIDATE_IDENTITY_COOKIE));
if (authResult == null || authResult.getSession() == null) {
if (authResult == null || authResult.session() == null) {
expireIdentityCookie(session);
return null;
}
authResult.getSession().setLastSessionRefresh(Time.currentTime());
authResult.session().setLastSessionRefresh(Time.currentTime());
return authResult;
}
@ -942,7 +942,7 @@ public class AuthenticationManager {
if (!compareSessionIdWithSessionCookie(session, userSession.getId())) {
AuthResult result = authenticateIdentityCookie(session, realm, false);
if (result != null) {
UserSessionModel oldSession = result.getSession();
UserSessionModel oldSession = result.session();
if (oldSession != null && !oldSession.getId().equals(userSession.getId())) {
logger.debugv("Removing old user session: session: {0}", oldSession.getId());
session.sessions().removeUserSession(realm, oldSession);
@ -1653,31 +1653,35 @@ public class AuthenticationManager {
SUCCESS, ACCOUNT_TEMPORARILY_DISABLED, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
}
public static class AuthResult {
private final UserModel user;
private final UserSessionModel session;
private final AccessToken token;
private final ClientModel client;
public AuthResult(UserModel user, UserSessionModel session, AccessToken token, ClientModel client) {
this.user = user;
this.session = session;
this.token = token;
this.client = client;
}
public record AuthResult(UserModel user, UserSessionModel session, AccessToken token, ClientModel client) {
/**
* @deprecated use {@link #session()} instead.
*/
@Deprecated(since = "26.5", forRemoval = true)
public UserSessionModel getSession() {
return session;
}
/**
* @deprecated use {@link #user()} instead.
*/
@Deprecated(since = "26.5", forRemoval = true)
public UserModel getUser() {
return user;
}
/**
* @deprecated use {@link #token()} instead.
*/
@Deprecated(since = "26.5", forRemoval = true)
public AccessToken getToken() {
return token;
}
/**
* @deprecated use {@link #client()} instead.
*/
@Deprecated(since = "26.5", forRemoval = true)
public ClientModel getClient() {
return client;
}

View File

@ -37,7 +37,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.SessionExpiration;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.sessions.AuthenticationSessionModel;
@ -53,6 +52,7 @@ import static org.keycloak.services.managers.AuthenticationManager.authenticateI
public class AuthenticationSessionManager {
private static final Logger log = Logger.getLogger(AuthenticationSessionManager.class);
private static final Base64.Encoder BASE_64_ENCODER_NO_PADDING = Base64.getEncoder().withoutPadding();
private final KeycloakSession session;
@ -60,13 +60,10 @@ public class AuthenticationSessionManager {
this.session = session;
}
/**
* Creates a fresh authentication session for the given realm . Optionally sets the browser
* authentication session cookie with the ID of the new session.
* @param realm
* @param browserCookie Set the cookie in the browser for the
* @return
*/
public RootAuthenticationSessionModel createAuthenticationSession(RealmModel realm, boolean browserCookie) {
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
@ -80,46 +77,29 @@ public class AuthenticationSessionManager {
}
public RootAuthenticationSessionModel getCurrentRootAuthenticationSession(RealmModel realm) {
String oldEncodedId = getAuthSessionCookies(realm);
if (oldEncodedId == null) {
AuthSessionCookie authSession = getAuthSessionCookies(realm);
if (authSession == null) {
return null;
}
AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId);
String sessionId = authSessionId.getDecodedId();
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, sessionId);
if (rootAuthSession != null) {
reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);
return rootAuthSession;
} else {
return null;
}
reEncodeAuthSessionCookie(authSession);
return authSession.rootSession();
}
/**
* Returns current authentication session if it exists, otherwise returns {@code null}.
* @param realm
* @return
* @return The current authentication session if it exists, otherwise returns {@code null}.
*/
public AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm, ClientModel client, String tabId) {
String oldEncodedId = getAuthSessionCookies(realm);
if (oldEncodedId == null) {
AuthSessionCookie rootAuth = getAuthSessionCookies(realm);
if (rootAuth == null) {
return null;
}
AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId);
String sessionId = authSessionId.getDecodedId();
AuthenticationSessionModel authSession = getAuthenticationSessionByIdAndClient(realm, sessionId, client, tabId);
AuthenticationSessionModel authSession = rootAuth.rootSession().getAuthenticationSession(client, tabId);
if (authSession != null) {
reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);
return authSession;
} else {
return null;
reEncodeAuthSessionCookie(rootAuth);
}
return authSession;
}
/**
@ -139,48 +119,33 @@ public class AuthenticationSessionManager {
* @param authSessionId decoded authSessionId (without route info attached)
*/
public void setAuthSessionIdHashCookie(String authSessionId) {
String authSessionIdHash = Base64.getEncoder().withoutPadding().encodeToString(HashUtils.hash(JavaAlgorithm.SHA256, authSessionId.getBytes(StandardCharsets.UTF_8)));
String authSessionIdHash = BASE_64_ENCODER_NO_PADDING.encodeToString(HashUtils.hash(JavaAlgorithm.SHA256, authSessionId.getBytes(StandardCharsets.UTF_8)));
session.getProvider(CookieProvider.class).set(CookieType.AUTH_SESSION_ID_HASH, authSessionIdHash);
log.debugf("Set KC_AUTH_SESSION_HASH cookie with value %s", authSessionIdHash);
}
/**
*
* @param encodedAuthSessionId encoded ID with attached route in cluster environment (EG. "NWUxNjFlMDAtZDQyNi00ZWE2LTk4ZTktNTJlYjk4NDRlMmQ3L.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);
if (!KeycloakModelUtils.isValidUUID(decodedAuthSessionId)) {
decodedAuthSessionId = decodeBase64AndValidateSignature(decodedAuthSessionId, false);
}
return new AuthSessionId(decodedAuthSessionId, reencoded);
}
void reencodeAuthSessionCookie(String oldEncodedAuthSessionId, AuthSessionId newAuthSessionId, RealmModel realm) {
if (!oldEncodedAuthSessionId.equals(newAuthSessionId.getEncodedId())) {
log.debugf("Route changed. Will update authentication session cookie. Old: '%s', New: '%s'", oldEncodedAuthSessionId,
newAuthSessionId.getEncodedId());
setAuthSessionCookie(newAuthSessionId.getDecodedId());
private void reEncodeAuthSessionCookie(AuthSessionCookie authSessionCookie) {
if (authSessionCookie.routeChanged()) {
setAuthSessionCookie(authSessionCookie.sessionId());
}
}
public String decodeBase64AndValidateSignature(String encodedBase64AuthSessionId, boolean validate) {
try {
String decodedAuthSessionId = new String(Base64Url.decode(encodedBase64AuthSessionId), StandardCharsets.UTF_8);
if (decodedAuthSessionId.lastIndexOf(".") != -1) {
String authSessionId = decodedAuthSessionId.substring(0, decodedAuthSessionId.indexOf("."));
String signature = decodedAuthSessionId.substring(decodedAuthSessionId.indexOf(".") + 1);
return validate ? validateAuthSessionIdSignature(authSessionId, signature) : authSessionId;
int dotIndex = decodedAuthSessionId.lastIndexOf('.');
if (dotIndex == -1) {
//not found / invalid
return null;
}
String authSessionId = decodedAuthSessionId.substring(0, dotIndex);
if (!validate) {
return authSessionId;
}
String signature = decodedAuthSessionId.substring(dotIndex + 1);
return validateAuthSessionIdSignature(authSessionId, signature);
} catch (Exception e) {
log.errorf("Error decoding auth session id with value: %s", encodedBase64AuthSessionId, e);
}
@ -223,10 +188,9 @@ public class AuthenticationSessionManager {
}
/**
* @param realm
* @return the value of the AUTH_SESSION_ID cookie. It is assumed that values could be encoded with signature and with route added (EG. "NWUxNjFlMDAtZDQyNi00ZWE2LTk4ZTktNTJlYjk4NDRlMmQ3L.node1" )
*/
String getAuthSessionCookies(RealmModel realm) {
AuthSessionCookie getAuthSessionCookies(RealmModel realm) {
String oldEncodedId = session.getProvider(CookieProvider.class).get(CookieType.AUTH_SESSION_ID);
if (oldEncodedId == null || oldEncodedId.isEmpty()) {
return null;
@ -245,7 +209,15 @@ public class AuthenticationSessionManager {
// but make sure the root authentication session actually exists
// without this check there is a risk of resolving user sessions from invalid root authentication sessions as they share the same id
RootAuthenticationSessionModel rootAuthenticationSession = session.authenticationSessions().getRootAuthenticationSession(realm, decodedAuthSessionId);
return rootAuthenticationSession != null ? oldEncodedId : null;
if (rootAuthenticationSession == null) {
return null;
}
String reEncoded = routeEncoder.encodeSessionId(decodedAuthSessionId);
boolean routeChanged = !Objects.equals(oldEncodedId, reEncoded);
if (routeChanged) {
log.debugf("Route changed. Will update authentication session cookie. Old: '%s', New: '%s'", oldEncodedId, reEncoded);
}
return new AuthSessionCookie(rootAuthenticationSession, routeChanged);
}
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
@ -265,8 +237,7 @@ public class AuthenticationSessionManager {
/**
* Remove authentication session from root session. Possibly remove whole root authentication session if there are no other browser tabs
* @param realm
* @param authSession
*
* @return true if whole root authentication session was removed. False just if single tab was removed
*/
public boolean removeTabIdInAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession) {
@ -285,31 +256,28 @@ public class AuthenticationSessionManager {
* This happens when one browser tab successfully finished authentication (including required actions and consent screen if applicable)
* Just authenticationSession of the current browser tab is removed from "root authentication session" and other tabs are kept, so
* authentication can be automatically finished in other browser tabs (typically with authChecker.js javascript)
*
* @param realm
* @param authSession
*/
public void updateAuthenticationSessionAfterSuccessfulAuthentication(RealmModel realm, AuthenticationSessionModel authSession) {
boolean removedRootAuthSession = removeTabIdInAuthenticationSession(realm, authSession);
if (!removedRootAuthSession) {
if(realm.getSsoSessionIdleTimeout() < SessionExpiration.getAuthSessionLifespan(realm) && realm.getSsoSessionMaxLifespan() < SessionExpiration.getAuthSessionLifespan(realm)) {
removeAuthenticationSession(realm, authSession, true);
}
else {
RootAuthenticationSessionModel rootAuthSession = authSession.getParentSession();
// 1 minute by default. Same timeout, which is used for client to complete "authorization code" flow
// Very short timeout should be OK as when this cookie is set, other existing browser tabs are supposed to be refreshed immediately by JS script authChecker.js
// and login user automatically. No need to have authenticationSession and cookie living any longer
int authSessionExpiresIn = realm.getAccessCodeLifespan();
// Set timestamp to the past to make sure that authSession is scheduled for expiration in "authSessionExpiresIn" seconds
int authSessionExpirationTime = Time.currentTime() - SessionExpiration.getAuthSessionLifespan(realm) + authSessionExpiresIn;
rootAuthSession.setTimestamp(authSessionExpirationTime);
log.tracef("Removed authentication session of root session '%s' with tabId '%s'. But there are remaining tabs in the root session. Root authentication session will expire in %d seconds", rootAuthSession.getId(), authSession.getTabId(), authSessionExpiresIn);
}
if (removedRootAuthSession) {
return;
}
if (realm.getSsoSessionIdleTimeout() < SessionExpiration.getAuthSessionLifespan(realm) && realm.getSsoSessionMaxLifespan() < SessionExpiration.getAuthSessionLifespan(realm)) {
removeAuthenticationSession(realm, authSession, true);
return;
}
RootAuthenticationSessionModel rootAuthSession = authSession.getParentSession();
// 1 minute by default. Same timeout, which is used for client to complete "authorization code" flow
// Very short timeout should be OK as when this cookie is set, other existing browser tabs are supposed to be refreshed immediately by JS script authChecker.js
// and login user automatically. No need to have authenticationSession and cookie living any longer
int authSessionExpiresIn = realm.getAccessCodeLifespan();
// Set timestamp to the past to make sure that authSession is scheduled for expiration in "authSessionExpiresIn" seconds
int authSessionExpirationTime = Time.currentTime() - SessionExpiration.getAuthSessionLifespan(realm) + authSessionExpiresIn;
rootAuthSession.setTimestamp(authSessionExpirationTime);
log.tracef("Removed authentication session of root session '%s' with tabId '%s'. But there are remaining tabs in the root session. Root authentication session will expire in %d seconds", rootAuthSession.getId(), authSession.getTabId(), authSessionExpiresIn);
}
// Check to see if we already have authenticationSession with same ID
@ -330,41 +298,47 @@ public class AuthenticationSessionManager {
}
public UserSessionModel getUserSessionFromAuthenticationCookie(RealmModel realm) {
String oldEncodedId = getAuthSessionCookies(realm);
AuthSessionCookie rootAuth = getAuthSessionCookies(realm);
if (oldEncodedId == null) {
if (rootAuth == null) {
// ideally, we should not rely on auth session id to retrieve user sessions
// in case the auth session was removed, we fall back to the identity cookie
// we are here doing the user session lookup twice, however the second lookup is going to make sure the
// session exists in remote caches
AuthenticationManager.AuthResult authResult = authenticateIdentityCookie(session, realm, true);
if (authResult != null && authResult.getSession() != null) {
oldEncodedId = authResult.getSession().getId();
} else {
return null;
}
return getUserSessionFromIdentityCookie(realm);
}
AuthSessionId authSessionId = decodeAuthSessionId(oldEncodedId);
String sessionId = authSessionId.getDecodedId();
// TODO: remove this code once InfinispanUserSessionProvider is removed or no longer using any remote caches, as other implementations don't need this call.
// This will remove userSession "locally" if it doesn't exist on remoteCache
var userSessionProvider = getUserSessionProvider();
userSessionProvider.getUserSessionWithPredicate(realm, sessionId, false, Objects::isNull);
UserSessionModel userSession = userSessionProvider.getUserSession(realm, sessionId);
UserSessionModel userSession = getUserSessionProvider().getUserSession(realm, rootAuth.sessionId());
if (userSession != null) {
reencodeAuthSessionCookie(oldEncodedId, authSessionId, realm);
return userSession;
} else {
reEncodeAuthSessionCookie(rootAuth);
}
return userSession;
}
private UserSessionModel getUserSessionFromIdentityCookie(RealmModel realm) {
AuthenticationManager.AuthResult authResult = authenticateIdentityCookie(session, realm, true);
if (authResult == null) {
return null;
}
assert authResult.session() != null;
// if we reach this point, the cookie is not found. Set it.
setAuthSessionCookie(authResult.session().getId());
return authResult.session();
}
private UserSessionProvider getUserSessionProvider() {
return session.sessions();
}
record AuthSessionCookie(RootAuthenticationSessionModel rootSession, boolean routeChanged) {
public String sessionId() {
return rootSession.getId();
}
}
}

View File

@ -248,13 +248,12 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
return Response.status(302).location(builder.build()).build();
}
cookieResult.getSession();
event.session(cookieResult.getSession());
event.user(cookieResult.getUser());
event.detail(Details.USERNAME, cookieResult.getUser().getUsername());
event.session(cookieResult.session());
event.user(cookieResult.user());
event.detail(Details.USERNAME, cookieResult.user().getUsername());
AuthenticatedClientSessionModel clientSession = null;
for (AuthenticatedClientSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
for (AuthenticatedClientSessionModel cs : cookieResult.session().getAuthenticatedClientSessions().values()) {
if (cs.getClient().getClientId().equals(clientId)) {
byte[] decoded = Base64Url.decode(hash);
MessageDigest md = null;
@ -263,7 +262,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
} catch (NoSuchAlgorithmException e) {
throw new ErrorPageException(session, Response.Status.INTERNAL_SERVER_ERROR, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST);
}
String input = nonce + cookieResult.getSession().getId() + clientId + providerAlias;
String input = nonce + cookieResult.session().getId() + clientId + providerAlias;
byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
if (MessageDigest.isEqual(decoded, check)) {
clientSession = cs;
@ -311,7 +310,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
// Create AuthenticationSessionModel with same ID like userSession and refresh cookie
UserSessionModel userSession = cookieResult.getSession();
UserSessionModel userSession = cookieResult.session();
// Auth session with ID corresponding to our userSession may already exists in some rare cases (EG. if some client tried to login in another browser tab with "prompt=login")
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realmModel, userSession.getId());
@ -331,7 +330,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
authSession.setProtocol(client.getProtocol());
authSession.setRedirectUri(redirectUri);
authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.getSession().getId() + clientId + providerAlias);
authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.session().getId() + clientId + providerAlias);
event.detail(Details.CODE_ID, userSession.getId());
event.success();
@ -485,10 +484,10 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
.authenticate();
if (authResult != null) {
AccessToken token = authResult.getToken();
ClientModel clientModel = authResult.getClient();
AccessToken token = authResult.token();
ClientModel clientModel = authResult.client();
event.client(clientModel);
event.user(authResult.getUser());
event.user(authResult.user());
session.getContext().setClient(clientModel);
@ -506,14 +505,14 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
IdentityProviderModel identityProviderConfig = getIdentityProviderConfig(providerAlias);
if (identityProviderConfig.isStoreToken()) {
FederatedIdentityModel identity = this.session.users().getFederatedIdentity(this.realmModel, authResult.getUser(), providerAlias);
FederatedIdentityModel identity = this.session.users().getFederatedIdentity(this.realmModel, authResult.user(), providerAlias);
if (identity == null) {
return corsResponse(badRequest("User [" + authResult.getUser().getId() + "] is not associated with identity provider [" + providerAlias + "]."), clientModel);
return corsResponse(badRequest("User [" + authResult.user().getId() + "] is not associated with identity provider [" + providerAlias + "]."), clientModel);
}
if (identity.getToken() == null) {
return corsResponse(notFound("No token stored for user [" + authResult.getUser().getId() + "] with associated identity provider [" + providerAlias + "]."), clientModel);
return corsResponse(notFound("No token stored for user [" + authResult.user().getId() + "] with associated identity provider [" + providerAlias + "]."), clientModel);
}
String oldToken = identity.getToken();
@ -529,7 +528,7 @@ public class IdentityBrokerService implements UserAuthenticationIdentityProvider
if (!Objects.equals(oldToken, identity.getToken())) {
// The API of the IdentityProvider doesn't allow use to pass down the realm and the user, so we check if the token has changed,
// and then update the store.
session.users().updateFederatedIdentity(session.getContext().getRealm(), authResult.getUser(), identity);
session.users().updateFederatedIdentity(session.getContext().getRealm(), authResult.user(), identity);
}
}
}

View File

@ -133,7 +133,7 @@ public class LoginActionsServiceChecks {
AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, true);
if (authResult != null) {
UserSessionModel userSession = authResult.getSession();
UserSessionModel userSession = authResult.session();
if (!user.equals(userSession.getUser())) {
// do not allow authenticated users performing actions that are bound to other user and fire an event
// it might be an attempt to hijack a user account or perform actions on behalf of others

View File

@ -205,7 +205,7 @@ public class SessionCodeChecks {
if (response.getStatus() != Response.Status.FOUND.getStatusCode()) {
AuthenticationManager.AuthResult authResult = authenticateIdentityCookie(session, realm, false);
if (authResult != null && authResult.getSession() != null) {
if (authResult != null && authResult.session() != null) {
response = null;
if (client != null && clientData != null) {

View File

@ -83,7 +83,7 @@ public class AccountConsole implements AccountResourceProvider {
public void init() {
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
if (authResult != null) {
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
auth = new Auth(realm, authResult.token(), authResult.user(), client, authResult.session(), true);
}
}

View File

@ -119,24 +119,24 @@ public class AccountLoader {
throw new NotAuthorizedException("Bearer token required");
}
AccessToken accessToken = authResult.getToken();
AccessToken accessToken = authResult.token();
if (accessToken.getAudience() == null || accessToken.getResourceAccess(client.getClientId()) == null) {
// transform for introspection to get the required claims
AccessTokenIntrospectionProvider provider = (AccessTokenIntrospectionProvider) session.getProvider(TokenIntrospectionProvider.class,
AccessTokenIntrospectionProviderFactory.ACCESS_TOKEN_TYPE);
accessToken = provider.transformAccessToken(accessToken, authResult.getSession());
accessToken = provider.transformAccessToken(accessToken, authResult.session());
}
if (!accessToken.hasAudience(client.getClientId())) {
throw new NotAuthorizedException("Invalid audience for client " + client.getClientId());
}
Auth auth = new Auth(session.getContext().getRealm(), accessToken, authResult.getUser(), client, authResult.getSession(), false);
Auth auth = new Auth(session.getContext().getRealm(), accessToken, authResult.user(), client, authResult.session(), false);
Cors.builder().allowedOrigins(auth.getToken()).allowedMethods("GET", "PUT", "POST", "DELETE").auth().add();
if (authResult.getUser().getServiceAccountClientLink() != null) {
if (authResult.user().getServiceAccountClientLink() != null) {
throw new NotAuthorizedException("Service accounts are not allowed to access this service");
}

View File

@ -229,7 +229,7 @@ public class AdminConsole {
throw new NotAuthorizedException("Bearer");
}
final String issuedFor = authResult.getToken().getIssuedFor();
final String issuedFor = authResult.token().getIssuedFor();
if (!Constants.ADMIN_CONSOLE_CLIENT_ID.equals(issuedFor)) {
if (issuedFor == null) {
throw new ForbiddenException("No azp claim in the token");
@ -241,7 +241,7 @@ public class AdminConsole {
}
}
UserModel user= authResult.getUser();
UserModel user= authResult.user();
String displayName;
if ((user.getFirstName() != null && !user.getFirstName().trim().equals("")) || (user.getLastName() != null && !user.getLastName().trim().equals(""))) {
displayName = user.getFirstName();
@ -277,7 +277,7 @@ public class AdminConsole {
Locale locale = session.getContext().resolveLocale(user);
return Cors.builder()
.allowedOrigins(authResult.getToken())
.allowedOrigins(authResult.token())
.allowedMethods("GET")
.auth()
.add(Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess, locale, Boolean.parseBoolean(user.getFirstAttribute(IS_TEMP_ADMIN_ATTR_NAME)))));

View File

@ -207,9 +207,9 @@ public class AdminRoot {
throw new NotAuthorizedException("Bearer");
}
session.getContext().setBearerToken(authResult.getToken());
session.getContext().setBearerToken(authResult.token());
return new AdminAuth(realm, authResult.getToken(), authResult.getUser(), authResult.getClient());
return new AdminAuth(realm, authResult.token(), authResult.user(), authResult.client());
}
public static UriBuilder realmsUrl(UriInfo uriInfo) {

View File

@ -51,7 +51,7 @@ public class ExampleRestResource {
private void checkRealmAdmin() {
if (auth == null) {
throw new NotAuthorizedException("Bearer");
} else if (auth.getToken().getRealmAccess() == null || !auth.getToken().getRealmAccess().isUserInRole("admin")) {
} else if (auth.token().getRealmAccess() == null || !auth.token().getRealmAccess().isUserInRole("admin")) {
throw new ForbiddenException("Does not have realm admin role");
}
}

View File

@ -61,7 +61,7 @@ public class OID4VCTargetRoleMapperTest extends OID4VCTest {
roleMapper.setMapperModel(pmm, "jwt_vc");
AppAuthManager.BearerTokenAuthenticator authenticator = new AppAuthManager.BearerTokenAuthenticator(session);
authenticator.setTokenString(token);
UserSessionModel userSessionModel = authenticator.authenticate().getSession();
UserSessionModel userSessionModel = authenticator.authenticate().session();
roleMapper.setClaimsForSubject(claimsMap, userSessionModel);
assertTrue("The roles should be included as a claim.", claimsMap.containsKey("roles"));
if (claimsMap.get("roles") instanceof HashSet roles) {

View File

@ -145,9 +145,9 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
protected static String prepareSessionCode(KeycloakSession session, AppAuthManager.BearerTokenAuthenticator authenticator, String note) {
AuthenticationManager.AuthResult authResult = authenticator.authenticate();
UserSessionModel userSessionModel = authResult.getSession();
UserSessionModel userSessionModel = authResult.session();
AuthenticatedClientSessionModel authenticatedClientSessionModel = userSessionModel.getAuthenticatedClientSessionByClient(
authResult.getClient().getId());
authResult.client().getId());
String codeId = SecretGenerator.getInstance().randomString();
String nonce = SecretGenerator.getInstance().randomString();
OAuth2Code oAuth2Code = new OAuth2Code(codeId,