Details
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 2ec8ec6..5072e1c 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -71,13 +71,11 @@ public class SamlProtocol implements LoginProtocol {
public static final String SAML_SERVER_SIGNATURE = "saml.server.signature";
public static final String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
public static final String SAML_AUTHNSTATEMENT = "saml.authnstatement";
- public static final String SAML_MULTIVALUED_ROLES = "saml.multivalued.roles";
public static final String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
public static final String SAML_ENCRYPT = "saml.encrypt";
public static final String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
public static final String SAML_REQUEST_ID = "SAML_REQUEST_ID";
public static final String SAML_LOGOUT_BINDING = "saml.logout.binding";
- public static final String SAML_LOGOUT_ISSUER = "saml.logout.issuer";
public static final String SAML_LOGOUT_REQUEST_ID = "SAML_LOGOUT_REQUEST_ID";
public static final String SAML_LOGOUT_RELAY_STATE = "SAML_LOGOUT_RELAY_STATE";
public static final String SAML_LOGOUT_BINDING_URI = "SAML_LOGOUT_BINDING_URI";
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index ad5b302..07b9d00 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -1,5 +1,6 @@
package org.keycloak.protocol.oidc.endpoints;
+import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
@@ -18,6 +19,7 @@ import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
@@ -42,6 +44,7 @@ import javax.ws.rs.core.UriInfo;
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class LogoutEndpoint {
+ protected static Logger logger = Logger.getLogger(LogoutEndpoint.class);
@Context
private KeycloakSession session;
@@ -78,28 +81,60 @@ public class LogoutEndpoint {
*/
@GET
@NoCache
- public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri) {
- if (redirectUri != null) {
- String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
+ public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, // deprecated
+ @QueryParam("id_token_hint") String encodedIdToken,
+ @QueryParam("post_logout_redirect_uri") String postLogoutRedirectUri,
+ @QueryParam("state") String state) {
+ String redirect = postLogoutRedirectUri != null ? postLogoutRedirectUri : redirectUri;
+
+ if (redirect != null) {
+ String validatedUri = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirect, realm);
if (validatedUri == null) {
event.event(EventType.LOGOUT);
- event.detail(Details.REDIRECT_URI, redirectUri);
+ event.detail(Details.REDIRECT_URI, redirect);
event.error(Errors.INVALID_REDIRECT_URI);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
}
- redirectUri = validatedUri;
+ redirect = validatedUri;
+ }
+
+ UserSessionModel userSession = null;
+ boolean error = false;
+ if (encodedIdToken != null) {
+ try {
+ IDToken idToken = tokenManager.verifyIDToken(realm, encodedIdToken);
+ userSession = session.sessions().getUserSession(realm, idToken.getSessionState());
+ if (userSession == null) {
+ error = true;
+ }
+ } catch (OAuthErrorException e) {
+ error = true;
+ }
+ if (error) {
+ event.event(EventType.LOGOUT);
+ event.error(Errors.INVALID_TOKEN);
+ return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
+ }
}
// 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) {
- if (redirectUri != null) authResult.getSession().setNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, redirectUri);
- authResult.getSession().setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, OIDCLoginProtocol.LOGIN_PROTOCOL);
+ userSession = userSession != null ? userSession : authResult.getSession();
+ if (redirectUri != 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);
return AuthenticationManager.browserLogout(session, realm, authResult.getSession(), uriInfo, clientConnection, headers);
+ } else if (userSession != null) { // non browser logout
+ event.event(EventType.LOGOUT);
+ authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+ event.user(userSession.getUser()).session(userSession).success();
}
if (redirectUri != null) {
- return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
+ UriBuilder uriBuilder = UriBuilder.fromUri(redirect);
+ if (state != null) uriBuilder.queryParam(OIDCLoginProtocol.STATE_PARAM, state);
+ return Response.status(302).location(uriBuilder.build()).build();
} else {
return Response.ok().build();
}
@@ -149,7 +184,7 @@ public class LogoutEndpoint {
}
private void logout(UserSessionModel userSession) {
- authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
+ authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
event.user(userSession.getUser()).session(userSession).success();
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index cdbef96..022b6ac 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -49,6 +49,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String LOGIN_PROTOCOL = "openid-connect";
public static final String STATE_PARAM = "state";
+ public static final String LOGOUT_STATE_PARAM = "OIDC_LOGOUT_STATE_PARAM";
public static final String SCOPE_PARAM = "scope";
public static final String CODE_PARAM = "code";
public static final String RESPONSE_TYPE_PARAM = "response_type";
@@ -182,6 +183,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override
public Response finishLogout(UserSessionModel userSession) {
String redirectUri = userSession.getNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI);
+ String state = userSession.getNote(OIDCLoginProtocol.LOGOUT_STATE_PARAM);
event.event(EventType.LOGOUT);
if (redirectUri != null) {
event.detail(Details.REDIRECT_URI, redirectUri);
@@ -190,7 +192,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
if (redirectUri != null) {
- return Response.status(302).location(UriBuilder.fromUri(redirectUri).build()).build();
+ UriBuilder uriBuilder = UriBuilder.fromUri(redirectUri);
+ if (state != null) uriBuilder.queryParam(STATE_PARAM, state);
+ return Response.status(302).location(uriBuilder.build()).build();
} else {
return Response.ok().build();
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index da9fdd8..01e244c 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -86,7 +86,7 @@ public class TokenManager {
UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
- AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers);
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
}
ClientSessionModel clientSession = null;
@@ -166,6 +166,26 @@ public class TokenManager {
}
return refreshToken;
}
+ public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException {
+ JWSInput jws = new JWSInput(encodedIDToken);
+ IDToken idToken = null;
+ try {
+ if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
+ }
+ idToken = jws.readJsonContent(IDToken.class);
+ } catch (IOException e) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
+ }
+ if (idToken.isExpired()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
+ }
+
+ if (idToken.getIssuedAt() < realm.getNotBefore()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
+ }
+ return idToken;
+ }
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
AccessToken token = initToken(realm, client, user, userSession, clientSession);
@@ -405,6 +425,7 @@ public class TokenManager {
idToken.issuedNow();
idToken.issuedFor(accessToken.getIssuedFor());
idToken.issuer(accessToken.getIssuer());
+ idToken.setSessionState(accessToken.getSessionState());
if (realm.getAccessTokenLifespan() > 0) {
idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 383f5e3..625845f 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -84,7 +84,7 @@ public class AuthenticationManager {
return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
}
- public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
+ public static void backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
if (userSession == null) return;
UserModel user = userSession.getUser();
userSession.setState(UserSessionModel.State.LOGGING_OUT);
@@ -461,7 +461,7 @@ public class AuthenticationManager {
UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
if (!isSessionValid(realm, userSession)) {
- if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers);
+ if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers);
logger.debug("User session not active");
return null;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index b4554ba..f9ee68b 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -586,7 +586,7 @@ public class LoginActionsService {
}
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
- AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
+ AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers);
event.error(Errors.INVALID_CODE);
return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
}