diff --git a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
index 6feb6558ef..c9399a5e1f 100755
--- a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
+++ b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java
@@ -26,4 +26,7 @@ public interface AdapterConstants {
// Attribute passed in registerNode request for register new application cluster node once he joined cluster
public static final String APPLICATION_CLUSTER_HOST = "application_cluster_host";
+
+ // Cookie used on adapter side to store token info. Used only when tokenStore is 'COOKIE'
+ public static final String KEYCLOAK_ADAPTER_STATE_COOKIE = "KEYCLOAK_ADAPTER_STATE";
}
diff --git a/core/src/main/java/org/keycloak/enums/TokenStore.java b/core/src/main/java/org/keycloak/enums/TokenStore.java
new file mode 100644
index 0000000000..de16ecd9de
--- /dev/null
+++ b/core/src/main/java/org/keycloak/enums/TokenStore.java
@@ -0,0 +1,9 @@
+package org.keycloak.enums;
+
+/**
+ * @author Marek Posolda
+ */
+public enum TokenStore {
+ SESSION,
+ COOKIE
+}
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index 778f9f5b34..e2fe9fdcf9 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -18,7 +18,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
"allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
"client-keystore", "client-keystore-password", "client-key-password",
"auth-server-url-for-backend-requests", "always-refresh-token",
- "register-node-at-startup", "register-node-period"
+ "register-node-at-startup", "register-node-period", "token-store"
})
public class AdapterConfig extends BaseAdapterConfig {
@@ -46,6 +46,8 @@ public class AdapterConfig extends BaseAdapterConfig {
protected boolean registerNodeAtStartup = false;
@JsonProperty("register-node-period")
protected int registerNodePeriod = -1;
+ @JsonProperty("token-store")
+ protected String tokenStore;
public boolean isAllowAnyHostname() {
return allowAnyHostname;
@@ -142,4 +144,12 @@ public class AdapterConfig extends BaseAdapterConfig {
public void setRegisterNodePeriod(int registerNodePeriod) {
this.registerNodePeriod = registerNodePeriod;
}
+
+ public String getTokenStore() {
+ return tokenStore;
+ }
+
+ public void setTokenStore(String tokenStore) {
+ this.tokenStore = tokenStore;
+ }
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
index 95e31d77ef..9fff2b26c8 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java
@@ -7,6 +7,7 @@ import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.PublishedRealmRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -257,6 +258,16 @@ public class AdapterDeploymentContext {
delegate.setSslRequired(sslRequired);
}
+ @Override
+ public TokenStore getTokenStore() {
+ return delegate.getTokenStore();
+ }
+
+ @Override
+ public void setTokenStore(TokenStore tokenStore) {
+ delegate.setTokenStore(tokenStore);
+ }
+
@Override
public String getStateCookieName() {
return delegate.getStateCookieName();
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
new file mode 100644
index 0000000000..fd2c5c3221
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterTokenStore.java
@@ -0,0 +1,43 @@
+package org.keycloak.adapters;
+
+import org.keycloak.KeycloakSecurityContext;
+
+/**
+ * Abstraction for storing token info on adapter side. Intended to be per-request object
+ *
+ * @author Marek Posolda
+ */
+public interface AdapterTokenStore {
+
+ /**
+ * Impl can validate if current token exists and perform refreshing if it exists and is expired
+ */
+ void checkCurrentToken();
+
+ /**
+ * Check if we are logged already (we have already valid and successfully refreshed accessToken). Establish security context if yes
+ *
+ * @param authenticator used for actual request authentication
+ * @return true if we are logged-in already
+ */
+ boolean isCached(RequestAuthenticator authenticator);
+
+ /**
+ * Finish successful OAuth2 login and store validated account
+ *
+ * @param account
+ */
+ void saveAccountInfo(KeycloakAccount account);
+
+ /**
+ * Handle logout on store side and possibly propagate logout call to Keycloak
+ */
+ void logout();
+
+ /**
+ * Callback invoked after successful token refresh
+ *
+ * @param securityContext context where refresh was performed
+ */
+ void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
index e41a8b5fc0..6e8b97caa1 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java
@@ -1,6 +1,11 @@
package org.keycloak.adapters;
+import java.util.Collections;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.representations.AccessToken;
import org.keycloak.util.UriUtils;
/**
@@ -8,6 +13,8 @@ import org.keycloak.util.UriUtils;
*/
public class AdapterUtils {
+ private static Logger log = Logger.getLogger(AdapterUtils.class);
+
public static String getOrigin(String browserRequestURL, KeycloakSecurityContext session) {
if (session instanceof RefreshableKeycloakSecurityContext) {
KeycloakDeployment deployment = ((RefreshableKeycloakSecurityContext)session).getDeployment();
@@ -26,4 +33,30 @@ public class AdapterUtils {
return UriUtils.getOrigin(browserRequestURL);
}
}
+
+ public static Set getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
+ Set roles = null;
+ AccessToken accessToken = session.getToken();
+ if (session.getDeployment().isUseResourceRoleMappings()) {
+ if (log.isTraceEnabled()) {
+ log.trace("useResourceRoleMappings");
+ }
+ AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
+ if (access != null) roles = access.getRoles();
+ } else {
+ if (log.isTraceEnabled()) {
+ log.trace("use realm role mappings");
+ }
+ AccessToken.Access access = accessToken.getRealmAccess();
+ if (access != null) roles = access.getRoles();
+ }
+ if (roles == null) roles = Collections.emptySet();
+ if (log.isTraceEnabled()) {
+ log.trace("Setting roles: ");
+ for (String role : roles) {
+ log.trace(" role: " + role);
+ }
+ }
+ return roles;
+ }
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
new file mode 100644
index 0000000000..23e017df68
--- /dev/null
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -0,0 +1,89 @@
+package org.keycloak.adapters;
+
+import java.io.IOException;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.RSATokenVerifier;
+import org.keycloak.VerificationException;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.IDToken;
+import org.keycloak.util.KeycloakUriBuilder;
+
+/**
+ * @author Marek Posolda
+ */
+public class CookieTokenStore {
+
+ private static final Logger log = Logger.getLogger(CookieTokenStore.class);
+ private static final String DELIM = "@";
+
+ public static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) {
+ log.infof("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
+ String accessToken = session.getTokenString();
+ String idToken = session.getIdTokenString();
+ String refreshToken = session.getRefreshToken();
+ String cookie = new StringBuilder(accessToken).append(DELIM)
+ .append(idToken).append(DELIM)
+ .append(refreshToken).toString();
+
+ String cookiePath = getContextPath(facade);
+ facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
+ }
+
+ public static KeycloakPrincipal getPrincipalFromCookie(KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) {
+ HttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
+ if (cookie == null) {
+ log.debug("Not found adapter state cookie in current request");
+ return null;
+ }
+
+ String cookieVal = cookie.getValue();
+
+ String[] tokens = cookieVal.split(DELIM);
+ if (tokens.length != 3) {
+ log.warnf("Invalid format of %s cookie. Count of tokens: %s, expected 3", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, tokens.length);
+ return null;
+ }
+
+ String accessTokenString = tokens[0];
+ String idTokenString = tokens[1];
+ String refreshTokenString = tokens[2];
+
+ try {
+ // Skip check if token is active now. It's supposed to be done later by the caller
+ AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealm(), false);
+ IDToken idToken;
+ if (idTokenString != null && idTokenString.length() > 0) {
+ JWSInput input = new JWSInput(idTokenString);
+ try {
+ idToken = input.readJsonContent(IDToken.class);
+ } catch (IOException e) {
+ throw new VerificationException(e);
+ }
+ } else {
+ idToken = null;
+ }
+
+ log.debug("Token Verification succeeded!");
+ RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
+ return new KeycloakPrincipal(accessToken.getSubject(), secContext);
+ } catch (VerificationException ve) {
+ log.warn("Failed verify token", ve);
+ return null;
+ }
+ }
+
+ public static void removeCookie(HttpFacade facade) {
+ String cookiePath = getContextPath(facade);
+ facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath);
+ }
+
+ private static String getContextPath(HttpFacade facade) {
+ String uri = facade.getRequest().getURI();
+ String path = KeycloakUriBuilder.fromUri(uri).getPath();
+ int index = path.indexOf("/", 1);
+ return index == -1 ? path : path.substring(0, index);
+ }
+}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index c2d60d10f1..2380684f9e 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -5,6 +5,7 @@ import org.jboss.logging.Logger;
import org.keycloak.ServiceUrlConstants;
import org.keycloak.enums.RelativeUrlsUsed;
import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.KeycloakUriBuilder;
@@ -42,6 +43,7 @@ public class KeycloakDeployment {
protected String scope;
protected SslRequired sslRequired = SslRequired.ALL;
+ protected TokenStore tokenStore = TokenStore.SESSION;
protected String stateCookieName = "OAuth_Token_Request_State";
protected boolean useResourceRoleMappings;
protected boolean cors;
@@ -236,6 +238,14 @@ public class KeycloakDeployment {
this.sslRequired = sslRequired;
}
+ public TokenStore getTokenStore() {
+ return tokenStore;
+ }
+
+ public void setTokenStore(TokenStore tokenStore) {
+ this.tokenStore = tokenStore;
+ }
+
public String getStateCookieName() {
return stateCookieName;
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 8913dd4e30..c79d90568d 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -4,6 +4,7 @@ import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jboss.logging.Logger;
import org.keycloak.enums.SslRequired;
+import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.PemUtils;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
@@ -48,6 +49,11 @@ public class KeycloakDeploymentBuilder {
} else {
deployment.setSslRequired(SslRequired.EXTERNAL);
}
+ if (adapterConfig.getTokenStore() != null) {
+ deployment.setTokenStore(TokenStore.valueOf(adapterConfig.getTokenStore().toUpperCase()));
+ } else {
+ deployment.setTokenStore(TokenStore.SESSION);
+ }
deployment.setResourceCredentials(adapterConfig.getCredentials());
deployment.setPublicClient(adapterConfig.isPublicClient());
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index c3c2e6e7ba..312576e046 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
+import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@@ -255,7 +256,8 @@ public abstract class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try {
- String httpSessionId = reqAuthenticator.getHttpSessionId(true);
+ // For COOKIE store we don't have httpSessionId and single sign-out won't be available
+ String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.getHttpSessionId(true) : null;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token");
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 3fc5b3d6e2..3cd87cb455 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
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
+import org.keycloak.enums.TokenStore;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -19,14 +20,16 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class);
protected transient KeycloakDeployment deployment;
+ protected transient AdapterTokenStore tokenStore;
protected String refreshToken;
public RefreshableKeycloakSecurityContext() {
}
- public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
+ public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
super(tokenString, token, idTokenString, idToken);
this.deployment = deployment;
+ this.tokenStore = tokenStore;
this.refreshToken = refreshToken;
}
@@ -42,6 +45,10 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
return super.getTokenString();
}
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
public void logout(KeycloakDeployment deployment) {
try {
ServerRequest.invokeLogout(deployment, refreshToken);
@@ -58,8 +65,9 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
return deployment;
}
- public void setDeployment(KeycloakDeployment deployment) {
+ public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
this.deployment = deployment;
+ this.tokenStore = tokenStore;
}
/**
@@ -107,8 +115,14 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.token = token;
this.refreshToken = response.getRefreshToken();
this.tokenString = tokenString;
+ tokenStore.refreshCallback(this);
return true;
}
+ protected void updateTokenCookie(KeycloakDeployment deployment, HttpFacade facade) {
+ if (deployment.getTokenStore() == TokenStore.COOKIE) {
+ CookieTokenStore.setTokenCookie(deployment, facade, this);
+ }
+ }
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
index 0b3123816d..a4da6a2842 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -12,12 +12,14 @@ public abstract class RequestAuthenticator {
protected HttpFacade facade;
protected KeycloakDeployment deployment;
+ protected AdapterTokenStore tokenStore;
protected AuthChallenge challenge;
protected int sslRedirectPort;
- public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+ public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
this.facade = facade;
this.deployment = deployment;
+ this.tokenStore = tokenStore;
this.sslRedirectPort = sslRedirectPort;
}
@@ -58,7 +60,7 @@ public abstract class RequestAuthenticator {
log.trace("try oauth");
}
- if (isCached()) {
+ if (tokenStore.isCached(this)) {
if (verifySSL()) return AuthOutcome.FAILED;
log.debug("AUTHENTICATED: was cached");
return AuthOutcome.AUTHENTICATED;
@@ -103,18 +105,17 @@ public abstract class RequestAuthenticator {
}
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
- RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
+ RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), session);
completeOAuthAuthentication(principal);
}
protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal principal);
- protected abstract boolean isCached();
protected abstract String getHttpSessionId(boolean create);
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
- RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);
+ RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), session);
completeBearerAuthentication(principal);
}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
new file mode 100644
index 0000000000..409f9c2c38
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaCookieTokenStore.java
@@ -0,0 +1,109 @@
+package org.keycloak.adapters.as7;
+
+import java.util.Set;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * per-request object
+ *
+ * @author Marek Posolda
+ */
+public class CatalinaCookieTokenStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger(CatalinaCookieTokenStore.class);
+
+ private Request request;
+ private HttpFacade facade;
+ private KeycloakDeployment deployment;
+
+ private KeycloakPrincipal authenticatedPrincipal;
+
+ public CatalinaCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) {
+ this.request = request;
+ this.facade = facade;
+ this.deployment = deployment;
+ }
+
+
+ @Override
+ public void checkCurrentToken() {
+ this.authenticatedPrincipal = checkPrincipalFromCookie();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
+ if (authenticatedPrincipal != null) {
+ log.debug("remote logged in already. Establish state from cookie");
+ RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+ securityContext.setCurrentRequestInfo(deployment, this);
+ Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
+
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+ }
+
+ @Override
+ public void logout() {
+ CookieTokenStore.removeCookie(facade);
+
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ if (ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
+ CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+ }
+
+ /**
+ * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
+ *
+ * @return valid principal
+ */
+ protected KeycloakPrincipal checkPrincipalFromCookie() {
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) {
+ log.debug("Account was not in cookie or was invalid");
+ return null;
+ }
+
+ RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return principal;
+
+ log.debugf("Cleanup and expire cookie for user %s after failed refresh", principal.getName());
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ CookieTokenStore.removeCookie(facade);
+ return null;
+ }
+}
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
index a1cb95c4eb..74e0e63919 100755
--- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java
@@ -1,21 +1,21 @@
package org.keycloak.adapters.as7;
-import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.connector.Request;
-import org.apache.catalina.realm.GenericPrincipal;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
-import org.keycloak.representations.AccessToken;
+import org.keycloak.enums.TokenStore;
import java.io.IOException;
import java.security.Principal;
-import java.util.Collections;
import java.util.Set;
import javax.servlet.http.HttpSession;
@@ -25,18 +25,17 @@ import javax.servlet.http.HttpSession;
* @version $Revision: 1 $
*/
public class CatalinaRequestAuthenticator extends RequestAuthenticator {
+
private static final Logger log = Logger.getLogger(CatalinaRequestAuthenticator.class);
protected KeycloakAuthenticatorValve valve;
- protected CatalinaUserSessionManagement userSessionManagement;
protected Request request;
public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
- KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
+ KeycloakAuthenticatorValve valve, AdapterTokenStore tokenStore,
CatalinaHttpFacade facade,
Request request) {
- super(facade, deployment, request.getConnector().getRedirectPort());
+ super(facade, deployment, tokenStore, request.getConnector().getRedirectPort());
this.valve = valve;
- this.userSessionManagement = userSessionManagement;
this.request = request;
}
@@ -46,7 +45,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override
protected void saveRequest() {
try {
- valve.keycloakSaveRequest(request);
+ // Support saving request just for TokenStore.SESSION TODO: Add to tokenStore spi?
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ valve.keycloakSaveRequest(request);
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -55,24 +57,36 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
@Override
- protected void completeOAuthAuthentication(KeycloakPrincipal skp) {
- RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ protected void completeOAuthAuthentication(final KeycloakPrincipal skp) {
+ final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ final Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ KeycloakAccount account = new KeycloakAccount() {
+
+ @Override
+ public Principal getPrincipal() {
+ return skp;
+ }
+
+ @Override
+ public Set getRoles() {
+ return roles;
+ }
+
+ @Override
+ public KeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+
+ };
+
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
- Set roles = getRolesFromToken(securityContext);
- GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
- Session session = request.getSessionInternal(true);
- session.setPrincipal(principal);
- session.setAuthType("OAUTH");
- session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
- String username = securityContext.getToken().getSubject();
- log.debug("userSessionManage.login: " + username);
- userSessionManagement.login(session);
+ this.tokenStore.saveAccountInfo(account);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
- Set roles = getRolesFromToken(securityContext);
+ Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isDebugEnabled()) {
log.debug("Completing bearer authentication. Bearer roles: " + roles);
}
@@ -82,39 +96,6 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
- protected Set getRolesFromToken(RefreshableKeycloakSecurityContext session) {
- Set roles = null;
- if (deployment.isUseResourceRoleMappings()) {
- AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
- if (access != null) roles = access.getRoles();
- } else {
- AccessToken.Access access = session.getToken().getRealmAccess();
- if (access != null) roles = access.getRoles();
- }
- if (roles == null) roles = Collections.emptySet();
- return roles;
- }
-
- @Override
- protected boolean isCached() {
- if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
- return false;
- log.debug("remote logged in already");
- GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
- request.setUserPrincipal(principal);
- request.setAuthType("KEYCLOAK");
- Session session = request.getSessionInternal();
- if (session != null) {
- RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
- if (securityContext != null) {
- securityContext.setDeployment(deployment);
- request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
- }
- }
- restoreRequest();
- return true;
- }
-
protected void restoreRequest() {
if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
if (valve.keycloakRestoreRequest(request)) {
diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
new file mode 100644
index 0000000000..08b83adf67
--- /dev/null
+++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaSessionTokenStore.java
@@ -0,0 +1,110 @@
+package org.keycloak.adapters.as7;
+
+import java.util.Set;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * per-request object
+ *
+ * @author Marek Posolda
+ */
+public class CatalinaSessionTokenStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger(CatalinaSessionTokenStore.class);
+
+ private Request request;
+ private KeycloakDeployment deployment;
+ private CatalinaUserSessionManagement sessionManagement;
+
+ public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, CatalinaUserSessionManagement sessionManagement) {
+ this.request = request;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
+ RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+ if (session == null) return;
+ // just in case session got serialized
+ if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+ // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
+ // not be updated
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
+
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ Session catalinaSession = request.getSessionInternal();
+ log.debugf("Cleanup and expire session %s after failed refresh", catalinaSession.getId());
+ catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ catalinaSession.setPrincipal(null);
+ catalinaSession.setAuthType(null);
+ catalinaSession.expire();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+ return false;
+ log.debug("remote logged in already. Establish state from session");
+ GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+ if (securityContext != null) {
+ securityContext.setCurrentRequestInfo(deployment, this);
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ }
+
+ ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ Set roles = account.getRoles();
+ GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), account.getPrincipal(), roles, securityContext);
+
+ Session session = request.getSessionInternal(true);
+ session.setPrincipal(principal);
+ session.setAuthType("OAUTH");
+ session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+ String username = securityContext.getToken().getSubject();
+ log.debug("userSessionManagement.login: " + username);
+ this.sessionManagement.login(session);
+ }
+
+ @Override
+ public void logout() {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ session.removeNote(KeycloakSecurityContext.class.getName());
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ if (ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+}
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 8b40abb567..93836a467d 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
@@ -13,17 +13,21 @@ import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.CookieTokenStore;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.enums.TokenStore;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -44,6 +48,9 @@ import java.io.InputStream;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+
+ public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+
private static final Logger log = Logger.getLogger(KeycloakAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected AdapterDeploymentContext deploymentContext;
@@ -63,14 +70,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) {
request.removeAttribute(KeycloakSecurityContext.class.getName());
- Session session = request.getSessionInternal(false);
- if (session != null) {
- session.removeNote(KeycloakSecurityContext.class.getName());
- if (ksc instanceof RefreshableKeycloakSecurityContext) {
- CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
- ((RefreshableKeycloakSecurityContext)ksc).logout(deploymentContext.resolveDeployment(facade));
- }
- }
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.logout();
}
super.logout(request);
}
@@ -164,10 +168,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
log.info("*** deployment isn't configured return false");
return false;
}
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
nodesRegistrationManagement.tryRegister(deployment);
- CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
+ CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, tokenStore, facade, request);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
@@ -188,27 +193,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
* @param request
*/
protected void checkKeycloakSession(Request request, HttpFacade facade) {
- if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
- RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
- if (session == null) return;
- // just in case session got serialized
- if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
- if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
-
- // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
- // not be updated
- boolean success = session.refreshExpiredToken(false);
- if (success && session.isActive()) return;
-
- // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
- Session catalinaSession = request.getSessionInternal();
- log.debugf("Cleanup and expire session %s after failed refresh", catalinaSession.getId());
- catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
- request.setUserPrincipal(null);
- request.setAuthType(null);
- catalinaSession.setPrincipal(null);
- catalinaSession.setAuthType(null);
- catalinaSession.expire();
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.checkCurrentToken();
}
public void keycloakSaveRequest(Request request) throws IOException {
@@ -223,4 +210,20 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
}
}
+ protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
+ AdapterTokenStore store = (AdapterTokenStore)request.getNote(TOKEN_STORE_NOTE);
+ if (store != null) {
+ return store;
+ }
+
+ if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
+ store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement);
+ } else {
+ store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment);
+ }
+
+ request.setNote(TOKEN_STORE_NOTE, store);
+ return store;
+ }
+
}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
new file mode 100644
index 0000000000..dec12b917d
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaCookieTokenStore.java
@@ -0,0 +1,107 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author Marek Posolda
+ */
+public class CatalinaCookieTokenStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger(""+CatalinaCookieTokenStore.class);
+
+ private Request request;
+ private HttpFacade facade;
+ private KeycloakDeployment deployment;
+
+ private KeycloakPrincipal authenticatedPrincipal;
+
+ public CatalinaCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) {
+ this.request = request;
+ this.facade = facade;
+ this.deployment = deployment;
+ }
+
+
+ @Override
+ public void checkCurrentToken() {
+ this.authenticatedPrincipal = checkPrincipalFromCookie();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
+ if (authenticatedPrincipal != null) {
+ log.fine("remote logged in already. Establish state from cookie");
+ RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+ securityContext.setCurrentRequestInfo(deployment, this);
+ Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
+
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+ }
+
+ @Override
+ public void logout() {
+ CookieTokenStore.removeCookie(facade);
+
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ if (ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
+ CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+ }
+
+ /**
+ * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
+ *
+ * @return valid principal
+ */
+ protected KeycloakPrincipal checkPrincipalFromCookie() {
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) {
+ log.fine("Account was not in cookie or was invalid");
+ return null;
+ }
+
+ RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return principal;
+
+ log.fine("Cleanup and expire cookie for user " + principal.getName() + " after failed refresh");
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ CookieTokenStore.removeCookie(facade);
+ return null;
+ }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
index 8516b1ad8b..cc9e56abd3 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java
@@ -1,20 +1,20 @@
package org.keycloak.adapters.tomcat7;
-import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.connector.Request;
-import org.apache.catalina.realm.GenericPrincipal;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
-import org.keycloak.representations.AccessToken;
+import org.keycloak.enums.TokenStore;
import java.io.IOException;
import java.security.Principal;
-import java.util.Collections;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -28,16 +28,14 @@ import javax.servlet.http.HttpSession;
public class CatalinaRequestAuthenticator extends RequestAuthenticator {
private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class);
protected KeycloakAuthenticatorValve valve;
- protected CatalinaUserSessionManagement userSessionManagement;
protected Request request;
public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
- KeycloakAuthenticatorValve valve, CatalinaUserSessionManagement userSessionManagement,
+ KeycloakAuthenticatorValve valve, AdapterTokenStore tokenStore,
CatalinaHttpFacade facade,
Request request) {
- super(facade, deployment, request.getConnector().getRedirectPort());
+ super(facade, deployment, tokenStore, request.getConnector().getRedirectPort());
this.valve = valve;
- this.userSessionManagement = userSessionManagement;
this.request = request;
}
@@ -47,7 +45,10 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
@Override
protected void saveRequest() {
try {
- valve.keycloakSaveRequest(request);
+ // Support saving request just for TokenStore.SESSION TODO: Add to tokenStore spi?
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ valve.keycloakSaveRequest(request);
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -56,24 +57,36 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
}
@Override
- protected void completeOAuthAuthentication(KeycloakPrincipal skp) {
- RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ protected void completeOAuthAuthentication(final KeycloakPrincipal skp) {
+ final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ final Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ KeycloakAccount account = new KeycloakAccount() {
+
+ @Override
+ public Principal getPrincipal() {
+ return skp;
+ }
+
+ @Override
+ public Set getRoles() {
+ return roles;
+ }
+
+ @Override
+ public KeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+
+ };
+
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
- Set roles = getRolesFromToken(securityContext);
- GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
- Session session = request.getSessionInternal(true);
- session.setPrincipal(principal);
- session.setAuthType("OAUTH");
- session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
- String username = securityContext.getToken().getSubject();
- log.finer("userSessionManagement.login: " + username);
- userSessionManagement.login(session);
+ this.tokenStore.saveAccountInfo(account);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
- Set roles = getRolesFromToken(securityContext);
+ Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isLoggable(Level.FINE)) {
log.fine("Completing bearer authentication. Bearer roles: " + roles);
}
@@ -83,39 +96,6 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
- protected Set getRolesFromToken(RefreshableKeycloakSecurityContext session) {
- Set roles = null;
- if (deployment.isUseResourceRoleMappings()) {
- AccessToken.Access access = session.getToken().getResourceAccess(deployment.getResourceName());
- if (access != null) roles = access.getRoles();
- } else {
- AccessToken.Access access = session.getToken().getRealmAccess();
- if (access != null) roles = access.getRoles();
- }
- if (roles == null) roles = Collections.emptySet();
- return roles;
- }
-
- @Override
- protected boolean isCached() {
- if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
- return false;
- log.finer("remote logged in already");
- GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
- request.setUserPrincipal(principal);
- request.setAuthType("KEYCLOAK");
- Session session = request.getSessionInternal();
- if (session != null) {
- RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getNote(KeycloakSecurityContext.class.getName());
- if (securityContext != null) {
- securityContext.setDeployment(deployment);
- request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
- }
- }
- restoreRequest();
- return true;
- }
-
protected void restoreRequest() {
if (request.getSessionInternal().getNote(Constants.FORM_REQUEST_NOTE) != null) {
if (valve.keycloakRestoreRequest(request)) {
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
new file mode 100644
index 0000000000..81a765bb1c
--- /dev/null
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaSessionTokenStore.java
@@ -0,0 +1,108 @@
+package org.keycloak.adapters.tomcat7;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author Marek Posolda
+ */
+public class CatalinaSessionTokenStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger(""+CatalinaSessionTokenStore.class);
+
+ private Request request;
+ private KeycloakDeployment deployment;
+ private CatalinaUserSessionManagement sessionManagement;
+
+ public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment, CatalinaUserSessionManagement sessionManagement) {
+ this.request = request;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) return;
+ RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+ if (session == null) return;
+ // just in case session got serialized
+ if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+ // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
+ // not be updated
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
+
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ Session catalinaSession = request.getSessionInternal();
+ log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
+ catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ catalinaSession.setPrincipal(null);
+ catalinaSession.setAuthType(null);
+ catalinaSession.expire();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
+ return false;
+ log.fine("remote logged in already. Establish state from session");
+ GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
+ if (securityContext != null) {
+ securityContext.setCurrentRequestInfo(deployment, this);
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ }
+
+ ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ Set roles = account.getRoles();
+ GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), account.getPrincipal(), roles, securityContext);
+
+ Session session = request.getSessionInternal(true);
+ session.setPrincipal(principal);
+ session.setAuthType("OAUTH");
+ session.setNote(KeycloakSecurityContext.class.getName(), securityContext);
+ String username = securityContext.getToken().getSubject();
+ log.fine("userSessionManagement.login: " + username);
+ this.sessionManagement.login(session);
+ }
+
+ @Override
+ public void logout() {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ session.removeNote(KeycloakSecurityContext.class.getName());
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
+ if (ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+}
diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
index 94447a1a59..0ffa42c857 100755
--- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
+++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/KeycloakAuthenticatorValve.java
@@ -15,6 +15,7 @@ import org.apache.catalina.deploy.LoginConfig;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterConstants;
import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
import org.keycloak.adapters.HttpFacade;
@@ -24,6 +25,7 @@ import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest;
+import org.keycloak.enums.TokenStore;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -45,6 +47,9 @@ import java.util.logging.Logger;
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+
+ public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+
private final static Logger log = Logger.getLogger(""+KeycloakAuthenticatorValve.class);
protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
protected AdapterDeploymentContext deploymentContext;
@@ -70,16 +75,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) {
request.removeAttribute(KeycloakSecurityContext.class.getName());
- Session session = request.getSessionInternal(false);
- if (session != null) {
- session.removeNote(KeycloakSecurityContext.class.getName());
- try {
- CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
- ServerRequest.invokeLogout(deploymentContext.resolveDeployment(facade), ksc.getToken().getSessionState());
- } catch (Exception e) {
- log.severe("failed to invoke remote logout. " + e.getMessage());
- }
- }
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.logout();
}
super.logout(request);
}
@@ -164,10 +164,11 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
if (deployment == null || !deployment.isConfigured()) {
return false;
}
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
nodesRegistrationManagement.tryRegister(deployment);
- CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, userSessionManagement, facade, request);
+ CatalinaRequestAuthenticator authenticator = new CatalinaRequestAuthenticator(deployment, this, tokenStore, facade, request);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
@@ -188,27 +189,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
* @param request
*/
protected void checkKeycloakSession(Request request, HttpFacade facade) {
- if (request.getSessionInternal(false) == null || request.getPrincipal() == null) return;
- RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
- if (session == null) return;
- // just in case session got serialized
- if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade));
- if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
-
- // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
- // not be updated
- boolean success = session.refreshExpiredToken(false);
- if (success && session.isActive()) return;
-
- // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
- Session catalinaSession = request.getSessionInternal();
- log.fine("Cleanup and expire session " + catalinaSession + " after failed refresh");
- catalinaSession.removeNote(KeycloakSecurityContext.class.getName());
- request.setUserPrincipal(null);
- request.setAuthType(null);
- catalinaSession.setPrincipal(null);
- catalinaSession.setAuthType(null);
- catalinaSession.expire();
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.checkCurrentToken();
}
public void keycloakSaveRequest(Request request) throws IOException {
@@ -223,4 +206,20 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
}
}
+ protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
+ AdapterTokenStore store = (AdapterTokenStore)request.getNote(TOKEN_STORE_NOTE);
+ if (store != null) {
+ return store;
+ }
+
+ if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
+ store = new CatalinaSessionTokenStore(request, resolvedDeployment, userSessionManagement);
+ } else {
+ store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment);
+ }
+
+ request.setNote(TOKEN_STORE_NOTE, store);
+ return store;
+ }
+
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
index 472bc90410..82c272733b 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -19,14 +19,15 @@ package org.keycloak.adapters.undertow;
import io.undertow.security.idm.Account;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
-import org.keycloak.representations.AccessToken;
import java.io.Serializable;
import java.security.Principal;
-import java.util.Collections;
import java.util.Set;
/**
@@ -40,33 +41,11 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
public KeycloakUndertowAccount(KeycloakPrincipal principal) {
this.principal = principal;
- setRoles(principal.getKeycloakSecurityContext().getToken());
+ setRoles(principal.getKeycloakSecurityContext());
}
- protected void setRoles(AccessToken accessToken) {
- Set roles = null;
- RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
- if (session.getDeployment().isUseResourceRoleMappings()) {
- if (log.isTraceEnabled()) {
- log.trace("useResourceRoleMappings");
- }
- AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
- if (access != null) roles = access.getRoles();
- } else {
- if (log.isTraceEnabled()) {
- log.trace("use realm role mappings");
- }
- AccessToken.Access access = accessToken.getRealmAccess();
- if (access != null) roles = access.getRoles();
- }
- if (roles == null) roles = Collections.emptySet();
- if (log.isTraceEnabled()) {
- log.trace("Setting roles: ");
- for (String role : roles) {
- log.trace(" role: " + role);
- }
- }
-
+ protected void setRoles(RefreshableKeycloakSecurityContext session) {
+ Set roles = AdapterUtils.getRolesFromSecurityContext(session);
this.accountRoles = roles;
}
@@ -85,11 +64,12 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
return principal.getKeycloakSecurityContext();
}
- public void setDeployment(KeycloakDeployment deployment) {
- principal.getKeycloakSecurityContext().setDeployment(deployment);
+ public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+ principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
}
- public boolean isActive() {
+ // Check if accessToken is active and try to refresh if it's not
+ public boolean checkActive() {
// this object may have been serialized, so we need to reset realm config/metadata
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
@@ -106,7 +86,7 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
}
log.debug("refresh succeeded");
- setRoles(session.getToken());
+ setRoles(session);
return true;
}
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 7303bda5bb..7f18e907e0 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
@@ -25,9 +25,12 @@ import io.undertow.servlet.handlers.ServletRequestContext;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -40,13 +43,11 @@ import javax.servlet.http.HttpSession;
public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
private static final Logger log = Logger.getLogger(ServletKeycloakAuthMech.class);
- protected UndertowUserSessionManagement userSessionManagement;
protected NodesRegistrationManagement nodesRegistrationManagement;
protected ConfidentialPortManager portManager;
public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager) {
- super(deploymentContext);
- this.userSessionManagement = userSessionManagement;
+ super(deploymentContext, userSessionManagement);
this.nodesRegistrationManagement = nodesRegistrationManagement;
this.portManager = portManager;
}
@@ -66,40 +67,12 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
return keycloakAuthenticate(exchange, securityContext, authenticator);
}
- @Override
- protected void registerNotifications(SecurityContext securityContext) {
-
- final NotificationReceiver logoutReceiver = new NotificationReceiver() {
- @Override
- public void handleNotification(SecurityNotification notification) {
- if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
- final ServletRequestContext servletRequestContext = notification.getExchange().getAttachment(ServletRequestContext.ATTACHMENT_KEY);
- HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
- req.removeAttribute(KeycloakUndertowAccount.class.getName());
- req.removeAttribute(KeycloakSecurityContext.class.getName());
- HttpSession session = req.getSession(false);
- if (session == null) return;
- KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
- if (account == null) return;
- session.removeAttribute(KeycloakSecurityContext.class.getName());
- session.removeAttribute(KeycloakUndertowAccount.class.getName());
- if (account.getKeycloakSecurityContext() != null) {
- UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
- account.getKeycloakSecurityContext().logout(deploymentContext.resolveDeployment(facade));
- }
- }
- };
-
- securityContext.registerNotificationReceiver(logoutReceiver);
- }
-
-
-
protected RequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
int confidentialPort = getConfidentilPort(exchange);
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
return new ServletRequestAuthenticator(facade, deployment,
- confidentialPort, securityContext, exchange, userSessionManagement);
+ confidentialPort, securityContext, exchange, tokenStore);
}
protected int getConfidentilPort(HttpServerExchange exchange) {
@@ -112,4 +85,13 @@ public class ServletKeycloakAuthMech extends UndertowKeycloakAuthMech {
return confidentialPort;
}
+ @Override
+ protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ return new ServletSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+ } else {
+ return new UndertowCookieTokenStore(facade, deployment, securityContext);
+ }
+ }
+
}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
index 5416d3c29e..ed13f5616f 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -18,13 +18,11 @@ package org.keycloak.adapters.undertow;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
-import io.undertow.server.session.Session;
import io.undertow.servlet.handlers.ServletRequestContext;
-import io.undertow.util.Sessions;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.HttpFacade;
-import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
@@ -41,34 +39,8 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
SecurityContext securityContext, HttpServerExchange exchange,
- UndertowUserSessionManagement userSessionManagement) {
- super(facade, deployment, sslRedirectPort, securityContext, exchange, userSessionManagement);
- }
-
- @Override
- protected boolean isCached() {
- HttpSession session = getSession(false);
- if (session == null) {
- log.debug("session was null, returning null");
- return false;
- }
- KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
- if (account == null) {
- log.debug("Account was not in session, returning null");
- return false;
- }
- account.setDeployment(deployment);
- if (account.isActive()) {
- log.debug("Cached account found");
- securityContext.authenticationComplete(account, "KEYCLOAK", false);
- propagateKeycloakContext( account);
- return true;
- } else {
- log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
- session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
- session.invalidate();
- return false;
- }
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
}
@Override
@@ -79,15 +51,6 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
req.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
}
- @Override
- protected void login(KeycloakAccount account) {
- final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
- HttpSession session = getSession(true);
- session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
- userSessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
-
- }
-
@Override
protected KeycloakUndertowAccount createAccount(KeycloakPrincipal principal) {
return new KeycloakUndertowAccount(principal);
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
new file mode 100644
index 0000000000..cbc7a0bcfe
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -0,0 +1,106 @@
+package org.keycloak.adapters.undertow;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in servlet session.
+ *
+ * @author Marek Posolda
+ */
+public class ServletSessionTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(ServletSessionTokenStore.class);
+
+ private final HttpServerExchange exchange;
+ private final KeycloakDeployment deployment;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+
+ public ServletSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext) {
+ this.exchange = exchange;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ HttpSession session = getSession(false);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) {
+ log.debug("Account was not in session, returning null");
+ return false;
+ }
+ account.setCurrentRequestInfo(deployment, this);
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ return true;
+ } else {
+ log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+ session.invalidate();
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSession session = getSession(true);
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+ }
+
+ @Override
+ public void logout() {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ req.removeAttribute(KeycloakUndertowAccount.class.getName());
+ req.removeAttribute(KeycloakSecurityContext.class.getName());
+ HttpSession session = req.getSession(false);
+ if (session == null) return;
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) return;
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ if (account.getKeycloakSecurityContext() != null) {
+ account.getKeycloakSecurityContext().logout(deployment);
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+
+ protected HttpSession getSession(boolean create) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ return req.getSession(create);
+ }
+
+}
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
new file mode 100644
index 0000000000..50859190fb
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
@@ -0,0 +1,79 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in cookie
+ *
+ * @author Marek Posolda
+ */
+public class UndertowCookieTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(UndertowCookieTokenStore.class);
+
+ private final HttpFacade facade;
+ private final KeycloakDeployment deployment;
+ private final SecurityContext securityContext;
+
+ public UndertowCookieTokenStore(HttpFacade facade, KeycloakDeployment deployment,
+ SecurityContext securityContext) {
+ this.facade = facade;
+ this.deployment = deployment;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) {
+ log.debug("Account was not in cookie or was invalid, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal);
+
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ return true;
+ } else {
+ log.debug("Account was not active, removing cookie and returning false");
+ CookieTokenStore.removeCookie(facade);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+ }
+
+ @Override
+ public void logout() {
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) return;
+
+ CookieTokenStore.removeCookie(facade);
+ principal.getKeycloakSecurityContext().logout(deployment);
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+ }
+}
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 d1b9e4e4ef..1c875cbfb7 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
@@ -24,10 +24,17 @@ import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Sessions;
+import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthChallenge;
import org.keycloak.adapters.AuthOutcome;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
/**
* Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
@@ -37,9 +44,11 @@ import org.keycloak.adapters.RequestAuthenticator;
public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanism {
public static final AttachmentKey KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
protected AdapterDeploymentContext deploymentContext;
+ protected UndertowUserSessionManagement sessionManagement;
- public UndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext) {
+ public UndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement) {
this.deploymentContext = deploymentContext;
+ this.sessionManagement = sessionManagement;
}
@Override
@@ -54,21 +63,17 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis
return new ChallengeResult(false);
}
- protected void registerNotifications(SecurityContext securityContext) {
+ protected void registerNotifications(final SecurityContext securityContext) {
final NotificationReceiver logoutReceiver = new NotificationReceiver() {
@Override
public void handleNotification(SecurityNotification notification) {
if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
- Session session = Sessions.getSession(notification.getExchange());
- if (session == null) return;
- KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
- if (account == null) return;
- session.removeAttribute(KeycloakUndertowAccount.class.getName());
- if (account.getKeycloakSecurityContext() != null) {
- UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
- account.getKeycloakSecurityContext().logout(deploymentContext.resolveDeployment(facade));
- }
+
+ UndertowHttpFacade facade = new UndertowHttpFacade(notification.getExchange());
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ AdapterTokenStore tokenStore = getTokenStore(notification.getExchange(), facade, deployment, securityContext);
+ tokenStore.logout();
}
};
@@ -96,4 +101,12 @@ public abstract class UndertowKeycloakAuthMech implements AuthenticationMechanis
return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
}
+ protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ return new UndertowSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+ } else {
+ return new UndertowCookieTokenStore(facade, deployment, securityContext);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
index 671f2e7c36..6b9c3518f7 100755
--- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -21,8 +21,8 @@ import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.Session;
import io.undertow.util.Sessions;
import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.HttpFacade;
-import org.keycloak.adapters.KeycloakAccount;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
@@ -36,16 +36,14 @@ import org.keycloak.adapters.RequestAuthenticator;
public abstract class UndertowRequestAuthenticator extends RequestAuthenticator {
protected SecurityContext securityContext;
protected HttpServerExchange exchange;
- protected UndertowUserSessionManagement userSessionManagement;
public UndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
SecurityContext securityContext, HttpServerExchange exchange,
- UndertowUserSessionManagement userSessionManagement) {
- super(facade, deployment, sslRedirectPort);
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, tokenStore, sslRedirectPort);
this.securityContext = securityContext;
this.exchange = exchange;
- this.userSessionManagement = userSessionManagement;
}
protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
@@ -67,16 +65,9 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
KeycloakUndertowAccount account = createAccount(principal);
securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext(account);
- login(account);
+ tokenStore.saveAccountInfo(account);
}
- protected void login(KeycloakAccount account) {
- Session session = Sessions.getOrCreateSession(exchange);
- session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
- userSessionManagement.login(session.getSessionManager());
- }
-
-
@Override
protected void completeBearerAuthentication(KeycloakPrincipal principal) {
KeycloakUndertowAccount account = createAccount(principal);
@@ -84,32 +75,6 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
propagateKeycloakContext(account);
}
- @Override
- protected boolean isCached() {
- Session session = Sessions.getSession(exchange);
- if (session == null) {
- log.debug("session was null, returning null");
- return false;
- }
- KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
- if (account == null) {
- log.debug("Account was not in session, returning null");
- return false;
- }
- account.setDeployment(deployment);
- if (account.isActive()) {
- log.debug("Cached account found");
- securityContext.authenticationComplete(account, "KEYCLOAK", false);
- propagateKeycloakContext( account);
- return true;
- } else {
- log.debug("Account was not active, returning false");
- session.removeAttribute(KeycloakUndertowAccount.class.getName());
- session.invalidate(exchange);
- return false;
- }
- }
-
@Override
protected String getHttpSessionId(boolean create) {
if (create) {
diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
new file mode 100644
index 0000000000..92848847ec
--- /dev/null
+++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -0,0 +1,90 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.util.Sessions;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakAccount;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in undertow session.
+ *
+ * @author Marek Posolda
+ */
+public class UndertowSessionTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(UndertowSessionTokenStore.class);
+
+ private final HttpServerExchange exchange;
+ private final KeycloakDeployment deployment;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+
+ public UndertowSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext) {
+ this.exchange = exchange;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ Session session = Sessions.getSession(exchange);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) {
+ log.debug("Account was not in session, returning null");
+ return false;
+ }
+ account.setCurrentRequestInfo(deployment, this);
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((UndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ return true;
+ } else {
+ log.debug("Account was not active, returning false");
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.invalidate(exchange);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(KeycloakAccount account) {
+ Session session = Sessions.getOrCreateSession(exchange);
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ sessionManagement.login(session.getSessionManager());
+ }
+
+ @Override
+ public void logout() {
+ Session session = Sessions.getSession(exchange);
+ if (session == null) return;
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) return;
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ if (account.getKeycloakSecurityContext() != null) {
+ account.getKeycloakSecurityContext().logout(deployment);
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+}
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
index cdb5315b97..6880d669eb 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
@@ -4,6 +4,7 @@ import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.api.ConfidentialPortManager;
import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
@@ -27,7 +28,8 @@ public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
@Override
protected ServletRequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
int confidentialPort = getConfidentilPort(exchange);
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
return new WildflyRequestAuthenticator(facade, deployment,
- confidentialPort, securityContext, exchange, userSessionManagement);
+ confidentialPort, securityContext, exchange, tokenStore);
}
}
diff --git a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
index bc8a6de787..84074d67f0 100755
--- a/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
+++ b/integration/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
@@ -8,6 +8,7 @@ import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
@@ -31,8 +32,8 @@ public class WildflyRequestAuthenticator extends ServletRequestAuthenticator {
public WildflyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
SecurityContext securityContext, HttpServerExchange exchange,
- UndertowUserSessionManagement userSessionManagement) {
- super(facade, deployment, sslRedirectPort, securityContext, exchange, userSessionManagement);
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
}
@Override
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 5443a7e742..cca4389041 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
@@ -32,7 +32,6 @@ import org.keycloak.adapters.AdapterConstants;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@@ -112,7 +111,7 @@ public class AdapterTest {
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false);
- AccessToken token = tm.createClientAccessToken(tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
+ AccessToken token = tm.createClientAccessToken(TokenManager.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);
@@ -440,7 +439,7 @@ public class AdapterTest {
// Open browser2
browser2.webRule.before();
try {
- browser2.loginAndCheckSession(browser2.driver, browser2.loginPage);
+ loginAndCheckSession(browser2.driver, browser2.loginPage);
// Logout in browser1
String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java
new file mode 100644
index 0000000000..e76d892173
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java
@@ -0,0 +1,180 @@
+package org.keycloak.testsuite.adapter;
+
+import java.net.URL;
+
+import javax.ws.rs.core.UriBuilder;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.adapters.AdapterConstants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.OpenIDConnectService;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
+import org.keycloak.testutils.KeycloakServer;
+import org.openqa.selenium.Cookie;
+import org.openqa.selenium.WebDriver;
+
+/**
+ * KEYCLOAK-702
+ *
+ * @author Marek Posolda
+ */
+public class CookieTokenStoreAdapterTest {
+
+ public static final String LOGIN_URL = OpenIDConnectService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString();
+
+ @ClassRule
+ public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() {
+
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm.json"), RealmRepresentation.class);
+ manager.importRealm(representation);
+
+ URL url = getClass().getResource("/adapter-test/cust-app-keycloak.json");
+ deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user");
+ url = getClass().getResource("/adapter-test/cust-app-cookie-keycloak.json");
+ deployApplication("customer-cookie-portal", "/customer-cookie-portal", CustomerServlet.class, url.getPath(), "user");
+ url = getClass().getResource("/adapter-test/customer-db-keycloak.json");
+ deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user");
+ }
+ };
+
+ @Rule
+ public WebRule webRule = new WebRule(this);
+
+ @WebResource
+ protected WebDriver driver;
+
+ @WebResource
+ protected OAuthClient oauth;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @Test
+ public void testTokenInCookieSSO() throws Throwable {
+ // Login
+ String tokenCookie = loginToCustomerCookiePortal();
+
+ // SSO to second app
+ driver.navigate().to("http://localhost:8081/customer-portal");
+ assertLogged();
+
+ // return to customer-cookie-portal and assert still same cookie (accessToken didn't expire)
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ assertLogged();
+ String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+ Assert.assertEquals(tokenCookie, tokenCookie2);
+
+ // Logout with httpServletRequest
+ logoutFromCustomerCookiePortal();
+
+ // Also should be logged-out from the second app
+ driver.navigate().to("http://localhost:8081/customer-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+ }
+
+ @Test
+ public void testTokenInCookieRefresh() throws Throwable {
+ // Set token timeout 1 sec
+ KeycloakSession session = keycloakRule.startSession();
+ RealmModel realm = session.realms().getRealmByName("demo");
+ int originalTokenTimeout = realm.getAccessTokenLifespan();
+ realm.setAccessTokenLifespan(1);
+ session.getTransaction().commit();
+ session.close();
+
+ // login to customer-cookie-portal
+ String tokenCookie1 = loginToCustomerCookiePortal();
+
+ // wait 2 secs
+ Thread.sleep(2000);
+
+ // assert cookie was refreshed
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+ assertLogged();
+ String tokenCookie2 = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+ Assert.assertNotEquals(tokenCookie1, tokenCookie2);
+
+ // login to 2nd app and logout from it
+ driver.navigate().to("http://localhost:8081/customer-portal");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal");
+ assertLogged();
+
+ driver.navigate().to("http://localhost:8081/customer-portal/logout");
+ driver.navigate().to("http://localhost:8081/customer-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+ // wait 2 secs until accessToken expires for customer-cookie-portal too.
+ Thread.sleep(2000);
+
+ // assert not logged in customer-cookie-portal
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+
+ // Change timeout back
+ session = keycloakRule.startSession();
+ realm = session.realms().getRealmByName("demo");
+ realm.setAccessTokenLifespan(originalTokenTimeout);
+ session.getTransaction().commit();
+ session.close();
+ }
+
+ @Test
+ public void testInvalidTokenCookie() throws Throwable {
+ // Login
+ String tokenCookie = loginToCustomerCookiePortal();
+ String changedTokenCookie = tokenCookie.replace("a", "b");
+
+ // change cookie to invalid value
+ driver.manage().addCookie(new Cookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, changedTokenCookie, "/customer-cookie-portal"));
+
+ // visit page and assert re-logged and cookie was refreshed
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+ String currentCookie = driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+ Assert.assertNotEquals(currentCookie, tokenCookie);
+ Assert.assertNotEquals(currentCookie, changedTokenCookie);
+
+ // logout
+ logoutFromCustomerCookiePortal();
+ }
+
+ // login to customer-cookie-portal and return the KEYCLOAK_ADAPTER_STATE cookie established on adapter
+ private String loginToCustomerCookiePortal() {
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+ loginPage.login("bburke@redhat.com", "password");
+ Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-cookie-portal");
+ assertLogged();
+
+ // Assert no JSESSIONID cookie
+ Assert.assertNull(driver.manage().getCookieNamed("JSESSIONID"));
+
+ return driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE).getValue();
+ }
+
+ private void logoutFromCustomerCookiePortal() {
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal/logout");
+ Assert.assertTrue(driver.getPageSource().contains("ok"));
+ Assert.assertNull(driver.manage().getCookieNamed(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE));
+ driver.navigate().to("http://localhost:8081/customer-cookie-portal");
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
+ }
+
+ private void assertLogged() {
+ String pageSource = driver.getPageSource();
+ Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen"));
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
index eafe55b5d3..a32bc1711c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CustomerServlet.java
@@ -29,8 +29,10 @@ public class CustomerServlet extends HttpServlet {
if (req.getRequestURI().toString().endsWith("logout")) {
resp.setStatus(200);
pw.println("ok");
- pw.flush();
+
+ // Call logout before pw.flush
req.logout();
+ pw.flush();
return;
}
KeycloakSecurityContext context = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
diff --git a/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json
new file mode 100644
index 0000000000..92fe860ae7
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/cust-app-cookie-keycloak.json
@@ -0,0 +1,12 @@
+{
+ "realm": "demo",
+ "resource": "customer-cookie-portal",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "http://localhost:8081/auth",
+ "ssl-required" : "external",
+ "expose-token": true,
+ "token-store": "cookie",
+ "credentials": {
+ "secret": "password"
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
index fc6ebaedbd..9a5da2428a 100755
--- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json
@@ -68,6 +68,15 @@
],
"secret": "password"
},
+ {
+ "name": "customer-cookie-portal",
+ "enabled": true,
+ "baseUrl": "http://localhost:8081/customer-cookie-portal",
+ "redirectUris": [
+ "http://localhost:8081/customer-cookie-portal/*"
+ ],
+ "secret": "password"
+ },
{
"name": "customer-portal-js",
"enabled": true,