mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
Ensure the logout endpoint removes the authentication session
Closes #43853
(cherry picked from commit 3b3adcf1e4)
Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
@@ -231,28 +231,6 @@ public class LogoutEndpoint {
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationSessionModel logoutSession = AuthenticationManager.createOrJoinLogoutSession(session, realm, new AuthenticationSessionManager(session), null, true, true);
|
||||
session.getContext().setAuthenticationSession(logoutSession);
|
||||
if (uiLocales != null) {
|
||||
logoutSession.setClientNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, uiLocales);
|
||||
}
|
||||
if (validatedRedirectUri != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, validatedRedirectUri);
|
||||
}
|
||||
if (state != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
|
||||
}
|
||||
if (initiatingIdp != null) {
|
||||
logoutSession.setAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP, initiatingIdp);
|
||||
}
|
||||
if (idToken != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_SESSION_STATE, idToken.getSessionState());
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_ISSUED_AT, String.valueOf(idToken.getIat()));
|
||||
}
|
||||
|
||||
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
|
||||
.setAuthenticationSession(logoutSession);
|
||||
|
||||
UserSessionModel userSession = null;
|
||||
|
||||
// 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
|
||||
@@ -274,6 +252,29 @@ public class LogoutEndpoint {
|
||||
userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
|
||||
}
|
||||
|
||||
AuthenticationSessionModel logoutSession = AuthenticationManager.createOrJoinLogoutSession(session, realm,
|
||||
new AuthenticationSessionManager(session), userSession, true, true);
|
||||
session.getContext().setAuthenticationSession(logoutSession);
|
||||
if (uiLocales != null) {
|
||||
logoutSession.setClientNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, uiLocales);
|
||||
}
|
||||
if (validatedRedirectUri != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, validatedRedirectUri);
|
||||
}
|
||||
if (state != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM, state);
|
||||
}
|
||||
if (initiatingIdp != null) {
|
||||
logoutSession.setAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP, initiatingIdp);
|
||||
}
|
||||
if (idToken != null) {
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_SESSION_STATE, idToken.getSessionState());
|
||||
logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_VALIDATED_ID_TOKEN_ISSUED_AT, String.valueOf(idToken.getIat()));
|
||||
}
|
||||
|
||||
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
|
||||
.setAuthenticationSession(logoutSession);
|
||||
|
||||
// Try to figure user because of localization
|
||||
if (userSession != null) {
|
||||
UserModel user = userSession.getUser();
|
||||
|
||||
@@ -27,12 +27,21 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.events.Details;
|
||||
@@ -45,6 +54,7 @@ import org.keycloak.protocol.RestartLoginCookie;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.AbstractChangeImportedUserPasswordsTest;
|
||||
@@ -72,6 +82,9 @@ import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
|
||||
import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.testsuite.util.WaitUtils;
|
||||
import org.keycloak.testsuite.util.oauth.OAuthClient;
|
||||
import org.keycloak.testsuite.util.oauth.PkceGenerator;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
|
||||
|
||||
/**
|
||||
@@ -727,6 +740,80 @@ public class MultipleTabsLoginTest extends AbstractChangeImportedUserPasswordsTe
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogoutDifferentBrowserWithAuthenticationSessionStillPresent() throws Exception {
|
||||
try (BrowserTabUtil tabUtil = BrowserTabUtil.getInstanceAndSetEnv(driver)) {
|
||||
// start login with the test-app
|
||||
oauth.client("test-app").openLoginForm();
|
||||
String tab1WindowHandle = tabUtil.getActualWindowHandle();
|
||||
loginPage.assertCurrent();
|
||||
|
||||
// create a second tab to initiate another login to the account-console
|
||||
tabUtil.newTab(oauth.client(Constants.ACCOUNT_CONSOLE_CLIENT_ID)
|
||||
.redirectUri(OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/account")
|
||||
.loginForm()
|
||||
.codeChallenge(PkceGenerator.s256())
|
||||
.build());
|
||||
assertThat(tabUtil.getCountOfTabs(), Matchers.is(2));
|
||||
loginPage.assertCurrent();
|
||||
tabUtil.switchToTab(tab1WindowHandle);
|
||||
tabUtil.closeTab(1);
|
||||
|
||||
// perform an online login to create the online session, auth session is maintained a short time because the other tab
|
||||
assertThat(tabUtil.getCountOfTabs(), Matchers.is(1));
|
||||
oauth.client("test-app", "password").redirectUri(OAuthClient.APP_ROOT + "/auth");
|
||||
loginPage.assertCurrent();
|
||||
loginPage.login("test-user@localhost", getPassword("test-user@localhost"));
|
||||
appPage.assertCurrent();
|
||||
AccessTokenResponse responseOnline = oauth.accessTokenRequest(oauth.parseLoginResponse().getCode()).send();
|
||||
Assert.assertNull(responseOnline.getError());
|
||||
RefreshToken onlineRefreshToken = oauth.parseRefreshToken(responseOnline.getRefreshToken());
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_REFRESH, onlineRefreshToken.getType());
|
||||
Assert.assertEquals("test-user@localhost", oauth.verifyToken(responseOnline.getAccessToken()).getPreferredUsername());
|
||||
|
||||
// perform an offline request for the client, automatic login
|
||||
oauth.scope("openid offline_access");
|
||||
oauth.openLoginForm();
|
||||
appPage.assertCurrent();
|
||||
AccessTokenResponse responseOffline = oauth.accessTokenRequest(oauth.parseLoginResponse().getCode()).send();
|
||||
Assert.assertNull(responseOffline.getError());
|
||||
RefreshToken offlineRefreshToken = oauth.parseRefreshToken(responseOffline.getRefreshToken());
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineRefreshToken.getType());
|
||||
Assert.assertEquals("test-user@localhost", oauth.verifyToken(responseOffline.getAccessToken()).getPreferredUsername());
|
||||
Assert.assertEquals(onlineRefreshToken.getSessionId(), offlineRefreshToken.getSessionId());
|
||||
|
||||
// remove the online session using logout but not having the cookies (different browser)
|
||||
HttpPost logoutPost = new HttpPost(OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/protocol/openid-connect/logout");
|
||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(
|
||||
List.of(new BasicNameValuePair(OAuth2Constants.ID_TOKEN_HINT, responseOnline.getIdToken())),
|
||||
StandardCharsets.UTF_8);
|
||||
logoutPost.setEntity(formEntity);
|
||||
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build();
|
||||
CloseableHttpResponse logoutResponse = httpClient.execute(logoutPost)) {
|
||||
Assert.assertEquals(Response.Status.OK.getStatusCode(), logoutResponse.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
// perform a second offline login after logoput with another user, auth session should be different
|
||||
oauth.openLoginForm();
|
||||
loginPage.assertCurrent();
|
||||
loginPage.login("non-duplicate-email-user", getPassword("non-duplicate-email-user"));
|
||||
appPage.assertCurrent();
|
||||
responseOffline = oauth.accessTokenRequest(oauth.parseLoginResponse().getCode()).send();
|
||||
Assert.assertNull(responseOffline.getError());
|
||||
offlineRefreshToken = oauth.parseRefreshToken(responseOffline.getRefreshToken());
|
||||
System.err.println(responseOffline.getRefreshToken());
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, offlineRefreshToken.getType());
|
||||
Assert.assertEquals("non-duplicate-email-user", oauth.verifyToken(responseOffline.getAccessToken()).getPreferredUsername());
|
||||
Assert.assertNotEquals(onlineRefreshToken.getSessionId(), offlineRefreshToken.getSessionId());
|
||||
|
||||
// refresh the token and check everything is correct
|
||||
responseOffline = oauth.doRefreshTokenRequest(responseOffline.getRefreshToken());
|
||||
Assert.assertNull(responseOffline.getError());
|
||||
Assert.assertEquals(TokenUtil.TOKEN_TYPE_OFFLINE, oauth.parseRefreshToken(responseOffline.getRefreshToken()).getType());
|
||||
Assert.assertEquals("non-duplicate-email-user", oauth.verifyToken(responseOffline.getAccessToken()).getPreferredUsername());
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForAppPage(Runnable htmlUnitAction) {
|
||||
if (driver instanceof HtmlUnitDriver) {
|
||||
// authChecker.js javascript does not work with HtmlUnitDriver. So need to "refresh" the current browser tab by running the last action in order to simulate "already_logged_in"
|
||||
|
||||
Reference in New Issue
Block a user