oidc logout profile

This commit is contained in:
Bill Burke 2015-03-22 12:45:36 -04:00
parent 97d5f4aafc
commit f546358d66
6 changed files with 74 additions and 16 deletions

View file

@ -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";

View file

@ -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();
}

View file

@ -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());
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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);
}