From dc4e8603d7c0f419c29509f174cb848b43b71c56 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 8 Aug 2014 18:33:37 -0400 Subject: [PATCH] change logout behavior --- docbook/reference/en/en-US/master.xml | 2 + .../modules/MigrationFromOlderVersions.xml | 11 +++ .../en/en-US/modules/direct-access.xml | 34 ++++++++- docbook/reference/en/en-US/modules/logout.xml | 9 +++ .../org/keycloak/example/AdminClient.java | 12 ++- .../RefreshableKeycloakSecurityContext.java | 8 ++ .../org/keycloak/adapters/ServerRequest.java | 34 +++++++-- .../as7/KeycloakAuthenticatorValve.java | 6 +- .../undertow/ServletKeycloakAuthMech.java | 7 +- .../undertow/UndertowKeycloakAuthMech.java | 7 +- .../services/managers/TokenManager.java | 39 +++++----- .../services/resources/TokenService.java | 74 +++++++++++++++---- .../org/keycloak/testsuite/OAuthClient.java | 26 ++++++- .../testsuite/adapter/AdapterTest.java | 3 +- .../keycloak/testsuite/forms/LogoutTest.java | 30 +------- .../testsuite/oauth/AccessTokenTest.java | 9 ++- ...urceOwnerPasswordCredentialsGrantTest.java | 10 +-- 17 files changed, 225 insertions(+), 96 deletions(-) create mode 100755 docbook/reference/en/en-US/modules/logout.xml mode change 100644 => 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml index 0f57ea3e8b..077eb98817 100755 --- a/docbook/reference/en/en-US/master.xml +++ b/docbook/reference/en/en-US/master.xml @@ -12,6 +12,7 @@ + @@ -80,6 +81,7 @@ This one is short &JBossAdapter; &JavascriptAdapter; &InstalledApplications; + &Logout; diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml index aecaf9169f..7b5f56b9fc 100755 --- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -1,5 +1,16 @@ Migration from older versions + + Migrating from 1.0 Beta 4 to RC-1 + + + logout REST API has been refactored. The GET request on the logout URI does not take a session_state + parameter anymore. You must be logged in in order to log out the session. + You can also POST to the lougt REST URI. This action requires a valid refresh token to perform the logout. + The signature is the same as refresh token minus the grant type form parameter. See documentation for details. + + + Migrating from 1.0 Beta 1 to Beta 4 diff --git a/docbook/reference/en/en-US/modules/direct-access.xml b/docbook/reference/en/en-US/modules/direct-access.xml index 1074537b27..c08735b6ee 100755 --- a/docbook/reference/en/en-US/modules/direct-access.xml +++ b/docbook/reference/en/en-US/modules/direct-access.xml @@ -88,7 +88,7 @@ try { if (isPublic()) { // if client is public access type formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal")); } else { - String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret); + String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret"); post.setHeader("Authorization", authorization); } UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); @@ -125,4 +125,36 @@ GET /my/rest/api Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA + + To logout you must use the refresh token contained in the AccessTokenResponse object. + + + formparams = new ArrayList(); +if (isPublic()) { // if client is public access type + formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal")); +} else { + String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret"); + post.setHeader("Authorization", authorization); +} +formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, tokenResponse.getRefreshToken())); +HttpResponse response = null; +URI logoutUri = KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") + .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH) + .build("demo"); +HttpPost post = new HttpPost(logoutUri); +UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); +post.setEntity(form); +response = client.execute(post); +int status = response.getStatusLine().getStatusCode(); +HttpEntity entity = response.getEntity(); +if (status != 204) { + error(status, entity); +} +if (entity == null) { + return; +} +InputStream is = entity.getContent(); +if (is != null) is.close(); +]]> \ No newline at end of file diff --git a/docbook/reference/en/en-US/modules/logout.xml b/docbook/reference/en/en-US/modules/logout.xml new file mode 100755 index 0000000000..e2812b8c09 --- /dev/null +++ b/docbook/reference/en/en-US/modules/logout.xml @@ -0,0 +1,9 @@ +
+ Logout + + There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call + HttpServletRequest.logout(). + For any other browser application, you can point the browser at the url http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri. + This will log you out if you have an SSO session with your browser. + +
\ No newline at end of file diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java index 763dfeeea0..4e26fdfd89 100755 --- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java +++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java @@ -102,17 +102,23 @@ public class AdminClient { try { - HttpGet get = new HttpGet(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") + HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH) - .queryParam("session_state", res.getSessionState()) .build("demo")); - HttpResponse response = client.execute(get); + List formparams = new ArrayList(); + formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, res.getRefreshToken())); + formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "admin-client")); + HttpResponse response = client.execute(post); + boolean status = response.getStatusLine().getStatusCode() != 204; HttpEntity entity = response.getEntity(); if (entity == null) { return; } InputStream is = entity.getContent(); if (is != null) is.close(); + if (status) { + throw new RuntimeException("failed to logout"); + } } finally { client.getConnectionManager().shutdown(); } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java index 644249904f..5f4325ab40 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java @@ -42,6 +42,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext return super.getTokenString(); } + public void logout(KeycloakDeployment deployment) { + try { + ServerRequest.invokeLogout(deployment, refreshToken); + } catch (Exception e) { + log.error("failed to invoke remote logout", e); + } + } + public boolean isActive() { return this.token.isActive() && this.token.getIssuedAt() > deployment.getNotBefore(); } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java index ac08c808d2..ed8e91f75a 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java @@ -48,19 +48,41 @@ public class ServerRequest { } } - public static void invokeLogout(KeycloakDeployment deployment, String sessionId) throws IOException, HttpFailure { - URI uri = deployment.getLogoutUrl().clone().queryParam("session_state", sessionId).build(); - HttpGet logout = new HttpGet(uri); - HttpResponse response = deployment.getClient().execute(logout); + public static void invokeLogout(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure { + String client_id = deployment.getResourceName(); + Map credentials = deployment.getResourceCredentials(); + HttpClient client = deployment.getClient(); + URI uri = deployment.getLogoutUrl().clone().build(); + List formparams = new ArrayList(); + for (Map.Entry entry : credentials.entrySet()) { + formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); + HttpResponse response = null; + HttpPost post = new HttpPost(uri); + if (!deployment.isPublicClient()) { + String clientSecret = credentials.get(CredentialRepresentation.SECRET); + if (clientSecret != null) { + String authorization = BasicAuthHelper.createHeader(client_id, clientSecret); + post.setHeader("Authorization", authorization); + } + } else { + formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, client_id)); + } + + UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8"); + post.setEntity(form); + response = client.execute(post); int status = response.getStatusLine().getStatusCode(); HttpEntity entity = response.getEntity(); - if (status != 200) { + if (status != 204) { error(status, entity); } if (entity == null) { return; } - entity.getContent().close(); + InputStream is = entity.getContent(); + if (is != null) is.close(); } public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri) throws HttpFailure, IOException { diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java index 52945cb7a3..45707b4af7 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java @@ -64,10 +64,8 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif Session session = request.getSessionInternal(false); if (session != null) { session.removeNote(KeycloakSecurityContext.class.getName()); - try { - ServerRequest.invokeLogout(deploymentContext.getDeployment(), ksc.getToken().getSessionState()); - } catch (Exception e) { - log.error("failed to invoke remote logout", e); + if (ksc instanceof RefreshableKeycloakSecurityContext) { + ((RefreshableKeycloakSecurityContext)ksc).logout(deploymentContext.getDeployment()); } } } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java index 45522fd035..1ccce63c0e 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java @@ -81,11 +81,8 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech { if (account == null) return; session.removeAttribute(KeycloakSecurityContext.class.getName()); session.removeAttribute(KeycloakUndertowAccount.class.getName()); - String sessionId = account.getKeycloakSecurityContext().getToken().getSessionState(); - try { - ServerRequest.invokeLogout(deploymentContext.getDeployment(), sessionId); - } catch (Exception e) { - log.error("failed to invoke remote logout", e); + if (account.getKeycloakSecurityContext() != null) { + account.getKeycloakSecurityContext().logout(deploymentContext.getDeployment()); } } }; diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java index 8e55e5a22d..8182496002 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowKeycloakAuthMech.java @@ -72,11 +72,8 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName()); if (account == null) return; session.removeAttribute(KeycloakUndertowAccount.class.getName()); - String sessionId = account.getKeycloakSecurityContext().getToken().getSessionState(); - try { - ServerRequest.invokeLogout(deploymentContext.getDeployment(), sessionId); - } catch (Exception e) { - log.error("failed to invoke remote logout", e); + if (account.getKeycloakSecurityContext() != null) { + account.getKeycloakSecurityContext().logout(deploymentContext.getDeployment()); } } }; diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java index 0971a07dc3..0fc5834c7f 100755 --- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java +++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java @@ -64,23 +64,7 @@ public class TokenManager { } public AccessToken refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException { - JWSInput jws = new JWSInput(encodedRefreshToken); - RefreshToken refreshToken = null; - try { - if (!RSAProvider.verify(jws, realm.getPublicKey())) { - throw new RuntimeException("Invalid refresh token"); - } - refreshToken = jws.readJsonContent(RefreshToken.class); - } catch (IOException e) { - throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e); - } - if (refreshToken.isExpired()) { - throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired"); - } - - if (refreshToken.getIssuedAt() < realm.getNotBefore()) { - throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token"); - } + RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); audit.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()); @@ -122,6 +106,27 @@ public class TokenManager { return accessToken; } + public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException { + JWSInput jws = new JWSInput(encodedRefreshToken); + RefreshToken refreshToken = null; + try { + if (!RSAProvider.verify(jws, realm.getPublicKey())) { + throw new RuntimeException("Invalid refresh token"); + } + refreshToken = jws.readJsonContent(RefreshToken.class); + } catch (IOException e) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e); + } + if (refreshToken.isExpired()) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired"); + } + + if (refreshToken.getIssuedAt() < realm.getNotBefore()) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token"); + } + return refreshToken; + } + public AccessToken createClientAccessToken(Set requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel session) { AccessToken token = initToken(realm, client, user, session); for (RoleModel role : requestedRoles) { diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index cba599cdc3..df5fbc3115 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -31,6 +31,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.RefreshToken; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.ClientConnection; import org.keycloak.services.managers.AccessCode; @@ -1040,39 +1041,29 @@ public class TokenService { } /** - * Logout user session. + * Logout user session. User must be logged in via a session cookie. * - * @param sessionState * @param redirectUri * @return */ @Path("logout") @GET @NoCache - public Response logout(final @QueryParam("session_state") String sessionState, final @QueryParam("redirect_uri") String redirectUri) { + public Response logout(final @QueryParam("redirect_uri") String redirectUri) { // todo do we care if anybody can trigger this? audit.event(EventType.LOGOUT); if (redirectUri != null) { audit.detail(Details.REDIRECT_URI, redirectUri); } - if (sessionState != null) { - audit.session(sessionState); - } - // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways. AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false); if (authResult != null) { logout(authResult.getSession()); - } else if (sessionState != null) { - UserSessionModel userSession = session.sessions().getUserSession(realm, sessionState); - if (userSession != null) { - logout(userSession); - } else { - audit.error(Errors.USER_SESSION_NOT_FOUND); - } } else { audit.error(Errors.USER_NOT_LOGGED_IN); + OAuthFlows oauth = Flows.oauth(session, realm, request, uriInfo, clientConnection, authManager, tokenManager); + return oauth.forwardToSecurityFailure("Not logged in."); } if (redirectUri != null) { @@ -1088,6 +1079,61 @@ public class TokenService { } } + /** + * Logout a session via a non-browser invocation. Similar signature to refresh token except there is no grant_type. + * You must pass in the refresh token and + * authenticate the client if it is not public. + * + * If the client is a confidential client + * you must include the client-id (application name or oauth client name) and secret in an Basic Auth Authorization header. + * + * If the client is a public client, then you must include a "client_id" form parameter with the app's or oauth client's name. + * + * returns 204 if successful, 400 if not with a json error response. + * + * @param authorizationHeader + * @param form + * @return + */ + @Path("logout") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response logoutToken(final @HeaderParam(HttpHeaders.AUTHORIZATION) String authorizationHeader, + final MultivaluedMap form) { + logger.info("--> logoutToken"); + if (!checkSsl()) { + throw new NotAcceptableException("HTTPS required"); + } + + audit.event(EventType.LOGOUT); + + ClientModel client = authorizeClient(authorizationHeader, form, audit); + String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN); + if (refreshToken == null) { + Map error = new HashMap(); + error.put(OAuth2Constants.ERROR, OAuthErrorException.INVALID_REQUEST); + error.put(OAuth2Constants.ERROR_DESCRIPTION, "No refresh token"); + audit.error(Errors.INVALID_TOKEN); + logger.error("OAuth Error: no refresh token"); + return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); + } + try { + RefreshToken token = tokenManager.verifyRefreshToken(realm, refreshToken); + UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState()); + if (userSessionModel != null) { + logout(userSessionModel); + } + } catch (OAuthErrorException e) { + Map error = new HashMap(); + error.put(OAuth2Constants.ERROR, e.getError()); + if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription()); + audit.error(Errors.INVALID_TOKEN); + logger.error("OAuth Error", e); + return Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(); + } + return Cors.add(request, Response.noContent()).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); + } + private void logout(UserSessionModel userSession) { authManager.logout(session, realm, userSession, uriInfo, clientConnection); audit.user(userSession.getUser()).session(userSession).success(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java index 3a3f6c84ee..9a14580c4e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java @@ -171,11 +171,31 @@ public class OAuthClient { return new AccessTokenResponse(client.execute(post)); } - public HttpResponse doLogout(String redirectUri, String sessionState) throws IOException { + public HttpResponse doLogout(String refreshToken, String clientSecret) throws IOException { HttpClient client = new DefaultHttpClient(); - HttpGet get = new HttpGet(getLogoutUrl(redirectUri, sessionState)); + HttpPost post = new HttpPost(getLogoutUrl(null, null)); - return client.execute(get); + List parameters = new LinkedList(); + if (refreshToken != null) { + parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); + } + if (clientId != null && clientSecret != null) { + String authorization = BasicAuthHelper.createHeader(clientId, clientSecret); + post.setHeader("Authorization", authorization); + } + else if (clientId != null) { + parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, clientId)); + } + + UrlEncodedFormEntity formEntity; + try { + formEntity = new UrlEncodedFormEntity(parameters, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + post.setEntity(formEntity); + + return client.execute(post); } public AccessTokenResponse doRefreshTokenRequest(String refreshToken, String password) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 9bb0f49faa..6e746cc228 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -214,7 +214,8 @@ public class AdapterTest { driver.navigate().to("http://localhost:8081/customer-portal"); - Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + String currentUrl = driver.getCurrentUrl(); + Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); driver.navigate().to("http://localhost:8081/product-portal"); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java old mode 100644 new mode 100755 index 4ec9193fbd..26c59e7f65 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LogoutTest.java @@ -124,32 +124,15 @@ public class LogoutTest { String sessionId = events.expectLogin().assertEvent().getSessionId(); - // Login session 2 - WebDriver driver2 = WebRule.createWebDriver(); - - OAuthClient oauth2 = new OAuthClient(driver2); - oauth2.doLogin("test-user@localhost", "password"); - - String sessionId2 = events.expectLogin().assertEvent().getSessionId(); - assertNotEquals(sessionId, sessionId2); - // Check session 1 logged-in oauth.openLoginForm(); events.expectLogin().session(sessionId).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); - // Check session 2 logged-in - oauth2.openLoginForm(); - events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); - - // Logout session 1 by redirect + // Logout session 1 by redirect driver.navigate().to(oauth.getLogoutUrl(AppPage.baseUrl, null)); events.expectLogout(sessionId).detail(Details.REDIRECT_URI, AppPage.baseUrl).assertEvent(); - // Check session 2 logged-in - oauth2.openLoginForm(); - events.expectLogin().session(sessionId2).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); - - // Check session 1 not logged-in + // Check session 1 not logged-in oauth.openLoginForm(); assertEquals(oauth.getLoginFormUrl(), driver.getCurrentUrl()); @@ -157,19 +140,10 @@ public class LogoutTest { oauth.doLogin("test-user@localhost", "password"); String sessionId3 = events.expectLogin().assertEvent().getSessionId(); assertNotEquals(sessionId, sessionId3); - assertNotEquals(sessionId2, sessionId3); - - // Logout session 2 by session_state - oauth2.doLogout(null, sessionId2); - events.expectLogout(sessionId2).removeDetail(Details.REDIRECT_URI).assertEvent(); // Check session 3 logged-in oauth.openLoginForm(); events.expectLogin().session(sessionId3).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); - - // Check session 2 not logged-in - oauth2.openLoginForm(); - assertEquals(oauth2.getLoginFormUrl(), driver2.getCurrentUrl()); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index f2ce2b6a97..55df18f296 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -305,8 +305,13 @@ public class AccessTokenTest { { builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); URI logoutUri = TokenService.logoutUrl(builder).build("test"); - Response response = client.target(logoutUri).queryParam("session_state", tokenResponse.getSessionState()).request().get(); - Assert.assertEquals(200, response.getStatus()); + String header = BasicAuthHelper.createHeader("test-app", "password"); + Form form = new Form(); + form.param("refresh_token", tokenResponse.getRefreshToken()); + Response response = client.target(logoutUri).request() + .header(HttpHeaders.AUTHORIZATION, header) + .post(Entity.form(form)); + Assert.assertEquals(204, response.getStatus()); response.close(); } { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index 5a1dad3de9..1ebf3279d8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -131,13 +131,9 @@ public class ResourceOwnerPasswordCredentialsGrantTest { .removeDetail(Details.REDIRECT_URI) .assertEvent(); - HttpResponse logoutResponse = oauth.doLogout(null, accessToken.getSessionState()); - assertEquals(200, logoutResponse.getStatusLine().getStatusCode()); - events.expectLogout(accessToken.getSessionState()).removeDetail(Details.REDIRECT_URI).assertEvent(); - - logoutResponse = oauth.doLogout(null, accessToken.getSessionState()); - assertEquals(200, logoutResponse.getStatusLine().getStatusCode()); - events.expectLogout(accessToken.getSessionState()).user((String) null).removeDetail(Details.REDIRECT_URI).error(Errors.USER_SESSION_NOT_FOUND).assertEvent(); + HttpResponse logoutResponse = oauth.doLogout(response.getRefreshToken(), "secret"); + assertEquals(204, logoutResponse.getStatusLine().getStatusCode()); + events.expectLogout(accessToken.getSessionState()).client("resource-owner").removeDetail(Details.REDIRECT_URI).assertEvent(); response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret"); assertEquals(400, response.getStatusCode());