KEYCLOAK-3173 enable logout offline refresh token using OIDC logout endpoint
This commit is contained in:
parent
d8b3654011
commit
925d5e1dea
3 changed files with 50 additions and 8 deletions
|
@ -40,8 +40,10 @@ import org.keycloak.representations.RefreshToken;
|
|||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.UserSessionManager;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.Cors;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.GET;
|
||||
|
@ -181,9 +183,19 @@ public class LogoutEndpoint {
|
|||
}
|
||||
try {
|
||||
RefreshToken token = tokenManager.verifyRefreshToken(session, realm, refreshToken, false);
|
||||
UserSessionModel userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
|
||||
boolean offline = TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType());
|
||||
|
||||
UserSessionModel userSessionModel;
|
||||
if (offline) {
|
||||
UserSessionManager sessionManager = new UserSessionManager(session);
|
||||
userSessionModel = sessionManager.findOfflineUserSession(realm, token.getSessionState());
|
||||
} else {
|
||||
userSessionModel = session.sessions().getUserSession(realm, token.getSessionState());
|
||||
}
|
||||
|
||||
if (userSessionModel != null) {
|
||||
logout(userSessionModel);
|
||||
logout(userSessionModel, offline);
|
||||
}
|
||||
} catch (OAuthErrorException e) {
|
||||
event.error(Errors.INVALID_TOKEN);
|
||||
|
@ -192,8 +204,8 @@ public class LogoutEndpoint {
|
|||
return Cors.add(request, Response.noContent()).auth().allowedOrigins(uriInfo, client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build();
|
||||
}
|
||||
|
||||
private void logout(UserSessionModel userSession) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
|
||||
private void logout(UserSessionModel userSession, boolean offline) {
|
||||
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true, offline);
|
||||
event.user(userSession.getUser()).session(userSession).success();
|
||||
}
|
||||
|
||||
|
|
|
@ -155,6 +155,12 @@ public class AuthenticationManager {
|
|||
);
|
||||
}
|
||||
|
||||
public static void backchannelLogout(KeycloakSession session, RealmModel realm,
|
||||
UserSessionModel userSession, UriInfo uriInfo,
|
||||
ClientConnection connection, HttpHeaders headers,
|
||||
boolean logoutBroker) {
|
||||
backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not logout broker
|
||||
|
@ -169,7 +175,8 @@ public class AuthenticationManager {
|
|||
public static void backchannelLogout(KeycloakSession session, RealmModel realm,
|
||||
UserSessionModel userSession, UriInfo uriInfo,
|
||||
ClientConnection connection, HttpHeaders headers,
|
||||
boolean logoutBroker) {
|
||||
boolean logoutBroker,
|
||||
boolean offlineSession) {
|
||||
if (userSession == null) return;
|
||||
UserModel user = userSession.getUser();
|
||||
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
|
||||
|
@ -190,7 +197,12 @@ public class AuthenticationManager {
|
|||
}
|
||||
|
||||
userSession.setState(UserSessionModel.State.LOGGED_OUT);
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
|
||||
if (offlineSession) {
|
||||
session.sessions().removeOfflineUserSession(realm, userSession);
|
||||
} else {
|
||||
session.sessions().removeUserSession(realm, userSession);
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthenticationSessionModel createOrJoinLogoutSession(RealmModel realm, final AuthenticationSessionManager asm, boolean browserCookie) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.testsuite.oauth;
|
||||
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
|
@ -58,12 +59,11 @@ import org.keycloak.testsuite.util.RoleBuilder;
|
|||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
import org.keycloak.util.TokenUtil;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.ws.rs.NotFoundException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
import static org.keycloak.testsuite.admin.ApiUtil.findRealmRoleByName;
|
||||
|
@ -550,4 +550,22 @@ public class OfflineTokenTest extends AbstractKeycloakTest {
|
|||
List<Map<String, Object>> consents = user.getConsents();
|
||||
Assert.assertTrue(consents.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void offlineTokenLogout() throws Exception {
|
||||
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||
oauth.clientId("offline-client");
|
||||
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret1", "test-user@localhost", "password");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
|
||||
assertEquals(200, response.getStatusCode());
|
||||
|
||||
CloseableHttpResponse logoutResponse = oauth.doLogout(response.getRefreshToken(), "secret1");
|
||||
assertEquals(204, logoutResponse.getStatusLine().getStatusCode());
|
||||
|
||||
response = oauth.doRefreshTokenRequest(response.getRefreshToken(), "secret1");
|
||||
assertEquals(400, response.getStatusCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue