Binding brokering OIDC user sessions with the issuer of the ID Token to avoid looking up sessions by iterating over all brokers in a realm
Closes #32091 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
88a5c96fff
commit
079242c398
3 changed files with 29 additions and 21 deletions
|
@ -32,6 +32,7 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||||
public static final String USE_JWKS_URL = "useJwksUrl";
|
public static final String USE_JWKS_URL = "useJwksUrl";
|
||||||
public static final String VALIDATE_SIGNATURE = "validateSignature";
|
public static final String VALIDATE_SIGNATURE = "validateSignature";
|
||||||
public static final String IS_ACCESS_TOKEN_JWT = "isAccessTokenJWT";
|
public static final String IS_ACCESS_TOKEN_JWT = "isAccessTokenJWT";
|
||||||
|
public static final String ISSUER = "issuer";
|
||||||
|
|
||||||
public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
|
||||||
super(identityProviderModel);
|
super(identityProviderModel);
|
||||||
|
@ -49,10 +50,10 @@ public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIssuer() {
|
public String getIssuer() {
|
||||||
return getConfig().get("issuer");
|
return getConfig().get(ISSUER);
|
||||||
}
|
}
|
||||||
public void setIssuer(String issuer) {
|
public void setIssuer(String issuer) {
|
||||||
getConfig().put("issuer", issuer);
|
getConfig().put(ISSUER, issuer);
|
||||||
}
|
}
|
||||||
public String getLogoutUrl() {
|
public String getLogoutUrl() {
|
||||||
return getConfig().get("logoutUrl");
|
return getConfig().get("logoutUrl");
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.protocol.oidc;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
|
||||||
import org.keycloak.common.Profile.Feature;
|
import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
@ -1440,21 +1441,21 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogoutTokenValidationCode verifyLogoutToken(KeycloakSession session, RealmModel realm, String encodedLogoutToken) {
|
public LogoutTokenValidationCode verifyLogoutToken(KeycloakSession session, String encodedLogoutToken) {
|
||||||
Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken);
|
Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken);
|
||||||
if (!logoutTokenOptional.isPresent()) {
|
if (logoutTokenOptional.isEmpty()) {
|
||||||
return LogoutTokenValidationCode.DECODE_TOKEN_FAILED;
|
return LogoutTokenValidationCode.DECODE_TOKEN_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogoutToken logoutToken = logoutTokenOptional.get();
|
LogoutToken logoutToken = logoutTokenOptional.get();
|
||||||
List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(realm, session).toList();
|
List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(logoutToken, session).toList();
|
||||||
if (identityProviders.isEmpty()) {
|
if (identityProviders.isEmpty()) {
|
||||||
return LogoutTokenValidationCode.COULD_NOT_FIND_IDP;
|
return LogoutTokenValidationCode.COULD_NOT_FIND_IDP;
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<OIDCIdentityProvider> validOidcIdentityProviders =
|
Stream<OIDCIdentityProvider> validOidcIdentityProviders =
|
||||||
validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken, logoutToken);
|
validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken);
|
||||||
if (validOidcIdentityProviders.count() == 0) {
|
if (validOidcIdentityProviders.findAny().isEmpty()) {
|
||||||
return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED;
|
return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1491,15 +1492,13 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Stream<OIDCIdentityProvider> getValidOIDCIdentityProvidersForBackchannelLogout(RealmModel realm, KeycloakSession session, String encodedLogoutToken, LogoutToken logoutToken) {
|
public Stream<OIDCIdentityProvider> getValidOIDCIdentityProvidersForBackchannelLogout(KeycloakSession session, String encodedLogoutToken, LogoutToken logoutToken) {
|
||||||
return validateLogoutTokenAgainstIdpProvider(getOIDCIdentityProviders(realm, session), encodedLogoutToken, logoutToken);
|
return validateLogoutTokenAgainstIdpProvider(getOIDCIdentityProviders(logoutToken, session), encodedLogoutToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Stream<OIDCIdentityProvider> validateLogoutTokenAgainstIdpProvider(Stream<OIDCIdentityProvider> oidcIdps, String encodedLogoutToken, LogoutToken logoutToken) {
|
public Stream<OIDCIdentityProvider> validateLogoutTokenAgainstIdpProvider(Stream<OIDCIdentityProvider> oidcIdps, String encodedLogoutToken) {
|
||||||
return oidcIdps
|
return oidcIdps
|
||||||
.filter(oidcIdp -> oidcIdp.getConfig().getIssuer() != null)
|
|
||||||
.filter(oidcIdp -> oidcIdp.isIssuer(logoutToken.getIssuer(), null))
|
|
||||||
.filter(oidcIdp -> {
|
.filter(oidcIdp -> {
|
||||||
try {
|
try {
|
||||||
oidcIdp.validateToken(encodedLogoutToken);
|
oidcIdp.validateToken(encodedLogoutToken);
|
||||||
|
@ -1511,13 +1510,22 @@ public class TokenManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<OIDCIdentityProvider> getOIDCIdentityProviders(RealmModel realm, KeycloakSession session) {
|
private Stream<OIDCIdentityProvider> getOIDCIdentityProviders(LogoutToken logoutToken, KeycloakSession session) {
|
||||||
try {
|
try {
|
||||||
return session.identityProviders().getAllStream()
|
return session.identityProviders()
|
||||||
.map(idpModel ->
|
.getAllStream(Map.of(
|
||||||
IdentityBrokerService.getIdentityProviderFactory(session, idpModel).create(session, idpModel))
|
OIDCIdentityProviderConfig.ISSUER, logoutToken.getIssuer()
|
||||||
.filter(OIDCIdentityProvider.class::isInstance)
|
), -1, -1)
|
||||||
.map(OIDCIdentityProvider.class::cast);
|
.map(model -> {
|
||||||
|
var idp = IdentityBrokerService.getIdentityProviderFactory(session, model).create(session, model);
|
||||||
|
|
||||||
|
if (idp instanceof OIDCIdentityProvider oidcIdp) {
|
||||||
|
return oidcIdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull);
|
||||||
} catch (IdentityBrokerException e) {
|
} catch (IdentityBrokerException e) {
|
||||||
logger.warnf("LogoutToken verification with identity provider failed", e.getMessage());
|
logger.warnf("LogoutToken verification with identity provider failed", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ public class LogoutEndpoint {
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, realm, encodedLogoutToken);
|
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, encodedLogoutToken);
|
||||||
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
||||||
String errorMessage = validationCode.getErrorMessage();
|
String errorMessage = validationCode.getErrorMessage();
|
||||||
event.detail(Details.REASON, errorMessage);
|
event.detail(Details.REASON, errorMessage);
|
||||||
|
@ -565,8 +565,7 @@ public class LogoutEndpoint {
|
||||||
|
|
||||||
LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get();
|
LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get();
|
||||||
|
|
||||||
Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(realm,
|
Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(session, encodedLogoutToken, logoutToken)
|
||||||
session, encodedLogoutToken, logoutToken)
|
|
||||||
.map(idp -> idp.getConfig().getAlias());
|
.map(idp -> idp.getConfig().getAlias());
|
||||||
|
|
||||||
boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents()
|
boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents()
|
||||||
|
|
Loading…
Reference in a new issue