Optimize LogoutEndpoint.backchannelLogout endpoint
closes #32683 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
parent
4b95b42590
commit
866101e72e
4 changed files with 81 additions and 23 deletions
|
@ -21,4 +21,8 @@ public enum LogoutTokenValidationCode {
|
|||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
LogoutTokenValidationContext toCtx() {
|
||||
return new LogoutTokenValidationContext(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2024 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.protocol.oidc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.broker.oidc.OIDCIdentityProvider;
|
||||
import org.keycloak.representations.LogoutToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class LogoutTokenValidationContext {
|
||||
|
||||
private final LogoutToken logoutToken;
|
||||
private final LogoutTokenValidationCode status;
|
||||
private final List<OIDCIdentityProvider> validIdentityProviders;
|
||||
|
||||
LogoutTokenValidationContext(LogoutTokenValidationCode status) {
|
||||
this(status, null, null);
|
||||
}
|
||||
|
||||
LogoutTokenValidationContext(LogoutTokenValidationCode status, LogoutToken logoutToken, List<OIDCIdentityProvider> validIdentityProviders) {
|
||||
this.logoutToken = logoutToken;
|
||||
this.status = status;
|
||||
this.validIdentityProviders = validIdentityProviders;
|
||||
}
|
||||
|
||||
public LogoutToken getLogoutToken() {
|
||||
return logoutToken;
|
||||
}
|
||||
|
||||
public LogoutTokenValidationCode getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public List<OIDCIdentityProvider> getValidIdentityProviders() {
|
||||
return validIdentityProviders;
|
||||
}
|
||||
}
|
|
@ -1454,45 +1454,45 @@ public class TokenManager {
|
|||
}
|
||||
}
|
||||
|
||||
public LogoutTokenValidationCode verifyLogoutToken(KeycloakSession session, String encodedLogoutToken) {
|
||||
public LogoutTokenValidationContext verifyLogoutToken(KeycloakSession session, String encodedLogoutToken) {
|
||||
Optional<LogoutToken> logoutTokenOptional = toLogoutToken(encodedLogoutToken);
|
||||
if (logoutTokenOptional.isEmpty()) {
|
||||
return LogoutTokenValidationCode.DECODE_TOKEN_FAILED;
|
||||
return LogoutTokenValidationCode.DECODE_TOKEN_FAILED.toCtx();
|
||||
}
|
||||
|
||||
LogoutToken logoutToken = logoutTokenOptional.get();
|
||||
List<OIDCIdentityProvider> identityProviders = getOIDCIdentityProviders(logoutToken, session).toList();
|
||||
if (identityProviders.isEmpty()) {
|
||||
return LogoutTokenValidationCode.COULD_NOT_FIND_IDP;
|
||||
return LogoutTokenValidationCode.COULD_NOT_FIND_IDP.toCtx();
|
||||
}
|
||||
|
||||
Stream<OIDCIdentityProvider> validOidcIdentityProviders =
|
||||
validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken);
|
||||
if (validOidcIdentityProviders.findAny().isEmpty()) {
|
||||
return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED;
|
||||
List<OIDCIdentityProvider> validOidcIdentityProviders =
|
||||
validateLogoutTokenAgainstIdpProvider(identityProviders.stream(), encodedLogoutToken).toList();
|
||||
if (validOidcIdentityProviders.isEmpty()) {
|
||||
return LogoutTokenValidationCode.TOKEN_VERIFICATION_WITH_IDP_FAILED.toCtx();
|
||||
}
|
||||
|
||||
if (logoutToken.getSubject() == null && logoutToken.getSid() == null) {
|
||||
return LogoutTokenValidationCode.MISSING_SID_OR_SUBJECT;
|
||||
return LogoutTokenValidationCode.MISSING_SID_OR_SUBJECT.toCtx();
|
||||
}
|
||||
|
||||
if (!checkLogoutTokenForEvents(logoutToken)) {
|
||||
return LogoutTokenValidationCode.BACKCHANNEL_LOGOUT_EVENT_MISSING;
|
||||
return LogoutTokenValidationCode.BACKCHANNEL_LOGOUT_EVENT_MISSING.toCtx();
|
||||
}
|
||||
|
||||
if (logoutToken.getOtherClaims().get(NONCE) != null) {
|
||||
return LogoutTokenValidationCode.NONCE_CLAIM_IN_TOKEN;
|
||||
return LogoutTokenValidationCode.NONCE_CLAIM_IN_TOKEN.toCtx();
|
||||
}
|
||||
|
||||
if (logoutToken.getId() == null) {
|
||||
return LogoutTokenValidationCode.LOGOUT_TOKEN_ID_MISSING;
|
||||
return LogoutTokenValidationCode.LOGOUT_TOKEN_ID_MISSING.toCtx();
|
||||
}
|
||||
|
||||
if (logoutToken.getIat() == null) {
|
||||
return LogoutTokenValidationCode.MISSING_IAT_CLAIM;
|
||||
return LogoutTokenValidationCode.MISSING_IAT_CLAIM.toCtx();
|
||||
}
|
||||
|
||||
return LogoutTokenValidationCode.VALIDATION_SUCCESS;
|
||||
return new LogoutTokenValidationContext(LogoutTokenValidationCode.VALIDATION_SUCCESS, logoutToken, validOidcIdentityProviders);
|
||||
}
|
||||
|
||||
public Optional<LogoutToken> toLogoutToken(String encodedLogoutToken) {
|
||||
|
@ -1505,11 +1505,6 @@ public class TokenManager {
|
|||
}
|
||||
|
||||
|
||||
public Stream<OIDCIdentityProvider> getValidOIDCIdentityProvidersForBackchannelLogout(KeycloakSession session, String encodedLogoutToken, LogoutToken logoutToken) {
|
||||
return validateLogoutTokenAgainstIdpProvider(getOIDCIdentityProviders(logoutToken, session), encodedLogoutToken);
|
||||
}
|
||||
|
||||
|
||||
public Stream<OIDCIdentityProvider> validateLogoutTokenAgainstIdpProvider(Stream<OIDCIdentityProvider> oidcIdps, String encodedLogoutToken) {
|
||||
return oidcIdps
|
||||
.filter(oidcIdp -> {
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.utils.SystemClientUtil;
|
||||
import org.keycloak.protocol.oidc.BackchannelLogoutResponse;
|
||||
import org.keycloak.protocol.oidc.LogoutTokenValidationCode;
|
||||
import org.keycloak.protocol.oidc.LogoutTokenValidationContext;
|
||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
|
@ -554,18 +555,18 @@ public class LogoutEndpoint {
|
|||
Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, encodedLogoutToken);
|
||||
if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
||||
String errorMessage = validationCode.getErrorMessage();
|
||||
LogoutTokenValidationContext validationCtx = tokenManager.verifyLogoutToken(session, encodedLogoutToken);
|
||||
if (!validationCtx.getStatus().equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
|
||||
String errorMessage = validationCtx.getStatus().getErrorMessage();
|
||||
event.detail(Details.REASON, errorMessage);
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, errorMessage,
|
||||
Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get();
|
||||
LogoutToken logoutToken = validationCtx.getLogoutToken();
|
||||
|
||||
Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(session, encodedLogoutToken, logoutToken)
|
||||
Stream<String> identityProviderAliases = validationCtx.getValidIdentityProviders().stream()
|
||||
.map(idp -> idp.getConfig().getAlias());
|
||||
|
||||
boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents()
|
||||
|
|
Loading…
Reference in a new issue