KEYCLOAK-11773 Front-channel logout with identity brokering does not work after browser restart

This commit is contained in:
Martin Kanis 2019-11-27 13:47:43 +01:00 committed by Stian Thorgersen
parent 5fc39daad3
commit 73d1a26040
4 changed files with 59 additions and 10 deletions

View file

@ -116,9 +116,10 @@ public class LogoutEndpoint {
} }
UserSessionModel userSession = null; UserSessionModel userSession = null;
IDToken idToken = null;
if (encodedIdToken != null) { if (encodedIdToken != null) {
try { try {
IDToken idToken = tokenManager.verifyIDTokenSignature(session, encodedIdToken); idToken = tokenManager.verifyIDTokenSignature(session, encodedIdToken);
userSession = session.sessions().getUserSession(realm, idToken.getSessionState()); userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
if (userSession != null) { if (userSession != null) {
@ -135,14 +136,14 @@ public class LogoutEndpoint {
AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false); AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(session, realm, false);
if (authResult != null) { if (authResult != null) {
userSession = userSession != null ? userSession : authResult.getSession(); userSession = userSession != null ? userSession : authResult.getSession();
if (redirect != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirect); return initiateBrowserLogout(userSession, redirect, state, initiatingIdp);
if (state != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state); }
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL); else if (userSession != null) {
logger.debug("Initiating OIDC browser logout"); // identity cookie is missing but there's valid id_token_hint which matches session cookie => continue with browser logout
Response response = AuthenticationManager.browserLogout(session, realm, authResult.getSession(), session.getContext().getUri(), clientConnection, headers, initiatingIdp); if (idToken != null && idToken.getSessionState().equals(AuthenticationManager.getSessionIdFromSessionCookie(session))) {
logger.debug("finishing OIDC browser logout"); return initiateBrowserLogout(userSession, redirect, state, initiatingIdp);
return response; }
} else if (userSession != null) { // non browser logout // non browser logout
event.event(EventType.LOGOUT); event.event(EventType.LOGOUT);
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true); AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, true);
event.user(userSession.getUser()).session(userSession).success(); event.user(userSession.getUser()).session(userSession).success();
@ -245,4 +246,14 @@ public class LogoutEndpoint {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh toked issued before the user session started"); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh toked issued before the user session started");
} }
} }
private Response initiateBrowserLogout(UserSessionModel userSession, String redirect, String state, String initiatingIdp ) {
if (redirect != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirect);
if (state != null) userSession.setNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
logger.debug("Initiating OIDC browser logout");
Response response = AuthenticationManager.browserLogout(session, realm, userSession, session.getContext().getUri(), clientConnection, headers, initiatingIdp);
logger.debug("finishing OIDC browser logout");
return response;
}
} }

View file

@ -800,6 +800,21 @@ public class AuthenticationManager {
} }
public static String getSessionIdFromSessionCookie(KeycloakSession session) {
Cookie cookie = session.getContext().getRequestHeaders().getCookies().get(KEYCLOAK_SESSION_COOKIE);
if (cookie == null || "".equals(cookie.getValue())) {
logger.debugv("Could not find cookie: {0}", KEYCLOAK_SESSION_COOKIE);
return null;
}
String[] parts = cookie.getValue().split("/", 3);
if (parts.length != 3) {
logger.debugv("Cannot parse session value from: {0}", KEYCLOAK_SESSION_COOKIE);
return null;
}
return parts[2];
}
public static boolean isSSOAuthentication(AuthenticatedClientSessionModel clientSession) { public static boolean isSSOAuthentication(AuthenticatedClientSessionModel clientSession) {
String ssoAuth = clientSession.getNote(SSO_AUTH); String ssoAuth = clientSession.getNote(SSO_AUTH);
return Boolean.parseBoolean(ssoAuth); return Boolean.parseBoolean(ssoAuth);

View file

@ -253,12 +253,15 @@ public abstract class AbstractBaseBrokerTest extends AbstractKeycloakTest {
logoutFromRealm(realm, null); logoutFromRealm(realm, null);
} }
protected void logoutFromRealm(String realm, String initiatingIdp) { protected void logoutFromRealm(String realm, String initiatingIdp) { logoutFromRealm(realm, initiatingIdp, null); }
protected void logoutFromRealm(String realm, String initiatingIdp, String tokenHint) {
driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext) driver.navigate().to(BrokerTestTools.getAuthRoot(suiteContext)
+ "/auth/realms/" + realm + "/auth/realms/" + realm
+ "/protocol/" + "openid-connect" + "/protocol/" + "openid-connect"
+ "/logout?redirect_uri=" + encodeUrl(getAccountUrl(realm)) + "/logout?redirect_uri=" + encodeUrl(getAccountUrl(realm))
+ (!StringUtils.isBlank(initiatingIdp) ? "&initiating_idp=" + initiatingIdp : "") + (!StringUtils.isBlank(initiatingIdp) ? "&initiating_idp=" + initiatingIdp : "")
+ (!StringUtils.isBlank(tokenHint) ? "&id_token_hint=" + tokenHint : "")
); );
try { try {

View file

@ -5,6 +5,8 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.openqa.selenium.Cookie;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.List; import java.util.List;
@ -87,4 +89,22 @@ public class KcOidcBrokerLogoutTest extends AbstractBaseBrokerTest {
driver.navigate().to(getAccountUrl(REALM_PROV_NAME)); driver.navigate().to(getAccountUrl(REALM_PROV_NAME));
waitForPage(driver, "log in to provider", true); waitForPage(driver, "log in to provider", true);
} }
@Test
public void logoutAfterBrowserRestart() {
logInAsUserInIDPForFirstTime();
assertLoggedInAccountManagement();
Cookie identityCookie = driver.manage().getCookieNamed(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
String idToken = identityCookie.getValue();
// simulate browser restart by deleting an identity cookie
log.debugf("Deleting %s cookie", AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
driver.manage().deleteCookieNamed(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE);
logoutFromRealm(bc.consumerRealmName(), null, idToken);
driver.navigate().to(getAccountUrl(REALM_PROV_NAME));
waitForPage(driver, "log in to provider", true);
}
} }