Merge pull request #801 from mposolda/master
KEYCLOAK-702 - Added AdapterTokenStore spi. Possibility to save account ...
This commit is contained in:
commit
4b87b8f7e3
35 changed files with 1351 additions and 340 deletions
|
@ -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";
|
||||
}
|
||||
|
|
9
core/src/main/java/org/keycloak/enums/TokenStore.java
Normal file
9
core/src/main/java/org/keycloak/enums/TokenStore.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package org.keycloak.enums;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public enum TokenStore {
|
||||
SESSION,
|
||||
COOKIE
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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);
|
||||
}
|
|
@ -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<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
|
||||
Set<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext>(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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(oauth.getToken().getSubject(), session);
|
||||
completeOAuthAuthentication(principal);
|
||||
}
|
||||
|
||||
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
|
||||
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(bearer.getToken().getSubject(), session);
|
||||
completeBearerAuthentication(principal);
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<RefreshableKeycloakSecurityContext> 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<String> 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<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> 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;
|
||||
}
|
||||
}
|
|
@ -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<RefreshableKeycloakSecurityContext> skp) {
|
||||
RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
|
||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
|
||||
final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
|
||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
||||
KeycloakAccount account = new KeycloakAccount() {
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return skp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return securityContext;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
Set<String> 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<RefreshableKeycloakSecurityContext> principal) {
|
||||
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
||||
Set<String> roles = getRolesFromToken(securityContext);
|
||||
Set<String> 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<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
|
||||
Set<String> 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)) {
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<String> 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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<RefreshableKeycloakSecurityContext> 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<String> 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<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
|
||||
KeycloakPrincipal<RefreshableKeycloakSecurityContext> 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;
|
||||
}
|
||||
}
|
|
@ -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<RefreshableKeycloakSecurityContext> skp) {
|
||||
RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
|
||||
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
|
||||
final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
|
||||
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
|
||||
KeycloakAccount account = new KeycloakAccount() {
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return skp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
return securityContext;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
Set<String> 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<RefreshableKeycloakSecurityContext> principal) {
|
||||
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
|
||||
Set<String> roles = getRolesFromToken(securityContext);
|
||||
Set<String> 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<String> getRolesFromToken(RefreshableKeycloakSecurityContext session) {
|
||||
Set<String> 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)) {
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<String> 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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<RefreshableKeycloakSecurityContext> principal) {
|
||||
this.principal = principal;
|
||||
setRoles(principal.getKeycloakSecurityContext().getToken());
|
||||
setRoles(principal.getKeycloakSecurityContext());
|
||||
}
|
||||
|
||||
protected void setRoles(AccessToken accessToken) {
|
||||
Set<String> 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<String> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<RefreshableKeycloakSecurityContext> principal) {
|
||||
return new KeycloakUndertowAccount(principal);
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext> 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);
|
||||
}
|
||||
}
|
|
@ -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<AuthChallenge> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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<RefreshableKeycloakSecurityContext> 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) {
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
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"));
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue