Merge pull request #827 from mposolda/master

Remove realm property from KeycloakSecurityContext. Fix multitenancy on EAP 6.3
This commit is contained in:
Marek Posolda 2014-10-31 13:32:11 +01:00
commit fe27f5364a
11 changed files with 45 additions and 41 deletions

View file

@ -17,7 +17,6 @@ import java.io.Serializable;
public class KeycloakSecurityContext implements Serializable { public class KeycloakSecurityContext implements Serializable {
protected String tokenString; protected String tokenString;
protected String idTokenString; protected String idTokenString;
protected String realm;
// Don't store parsed tokens into HTTP session // Don't store parsed tokens into HTTP session
protected transient AccessToken token; protected transient AccessToken token;
@ -26,12 +25,11 @@ public class KeycloakSecurityContext implements Serializable {
public KeycloakSecurityContext() { public KeycloakSecurityContext() {
} }
public KeycloakSecurityContext(String tokenString, AccessToken token, String idTokenString, IDToken idToken, String realm) { public KeycloakSecurityContext(String tokenString, AccessToken token, String idTokenString, IDToken idToken) {
this.tokenString = tokenString; this.tokenString = tokenString;
this.token = token; this.token = token;
this.idToken = idToken; this.idToken = idToken;
this.idTokenString = idTokenString; this.idTokenString = idTokenString;
this.realm = realm;
} }
public AccessToken getToken() { public AccessToken getToken() {
@ -51,7 +49,8 @@ public class KeycloakSecurityContext implements Serializable {
} }
public String getRealm() { public String getRealm() {
return realm; // Assumption that issuer contains realm name
return token.getIssuer();
} }
// SERIALIZATION // SERIALIZATION

View file

@ -56,7 +56,6 @@ public class SkeletonKeyTokenTest {
@Test @Test
public void testSerialization() throws Exception { public void testSerialization() throws Exception {
String realm = "acme";
AccessToken token = createSimpleToken(); AccessToken token = createSimpleToken();
IDToken idToken = new IDToken(); IDToken idToken = new IDToken();
idToken.setEmail("joe@email.cz"); idToken.setEmail("joe@email.cz");
@ -70,7 +69,7 @@ public class SkeletonKeyTokenTest {
.jsonContent(idToken) .jsonContent(idToken)
.rsa256(keyPair.getPrivate()); .rsa256(keyPair.getPrivate());
KeycloakSecurityContext ctx = new KeycloakSecurityContext(encoded, token, encodedIdToken, idToken, realm); KeycloakSecurityContext ctx = new KeycloakSecurityContext(encoded, token, encodedIdToken, idToken);
KeycloakPrincipal principal = new KeycloakPrincipal("joe", ctx); KeycloakPrincipal principal = new KeycloakPrincipal("joe", ctx);
// Serialize // Serialize
@ -104,6 +103,7 @@ public class SkeletonKeyTokenTest {
private AccessToken createSimpleToken() { private AccessToken createSimpleToken() {
AccessToken token = new AccessToken(); AccessToken token = new AccessToken();
token.id("111"); token.id("111");
token.issuer("acme");
token.addAccess("foo").addRole("admin"); token.addAccess("foo").addRole("admin");
token.addAccess("bar").addRole("user"); token.addAccess("bar").addRole("user");
return token; return token;

View file

@ -1,7 +1,7 @@
Keycloak Example - Multi Tenancy Keycloak Example - Multi Tenancy
======================================= =======================================
The following example was tested on Wildfly 8.1.0.Final and should be compatible with any JBoss AS, JBoss EAP or Wildfly that supports Java EE 7. The following example was tested on Wildfly 8.1.0.Final and JBoss EAP 6.3. It should be compatible with any JBoss AS, JBoss EAP or Wildfly that supports Java EE 7.
This example demonstrates the simplest possible scenario for Keycloak Multi Tenancy support. Multi Tenancy is understood on this context as a single application (WAR) that is deployed on a single or clustered application server, authenticating users from *different realms* against a single or clustered Keycloak server. This example demonstrates the simplest possible scenario for Keycloak Multi Tenancy support. Multi Tenancy is understood on this context as a single application (WAR) that is deployed on a single or clustered application server, authenticating users from *different realms* against a single or clustered Keycloak server.

View file

@ -26,7 +26,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
} }
public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, 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, deployment.getRealm()); super(tokenString, token, idTokenString, idToken);
this.deployment = deployment; this.deployment = deployment;
this.tokenStore = tokenStore; this.tokenStore = tokenStore;
this.refreshToken = refreshToken; this.refreshToken = refreshToken;
@ -67,7 +67,6 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) { public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
this.deployment = deployment; this.deployment = deployment;
this.tokenStore = tokenStore; this.tokenStore = tokenStore;
this.realm = deployment.getRealm();
} }
/** /**
@ -84,7 +83,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession? if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
if (!this.realm.equals(this.deployment.getRealm())) { if (!this.getRealm().equals(this.deployment.getRealm())) {
// this should not happen, but let's check it anyway // this should not happen, but let's check it anyway
return false; return false;
} }

View file

@ -49,6 +49,12 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
if (authenticatedPrincipal != null) { if (authenticatedPrincipal != null) {
log.debug("remote logged in already. Establish state from cookie"); log.debug("remote logged in already. Establish state from cookie");
RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
if (!securityContext.getRealm().equals(deployment.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this); securityContext.setCurrentRequestInfo(deployment, this);
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext); Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
@ -97,11 +103,6 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
if (!session.getRealm().equals(deployment.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return null;
}
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal; if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
boolean success = session.refreshExpiredToken(false); boolean success = session.refreshExpiredToken(false);
if (success && session.isActive()) return principal; if (success && session.isActive()) return principal;

View file

@ -38,11 +38,6 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName()); RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (session == null) return; if (session == null) return;
if (!deployment.getRealm().equals(session.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return;
}
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this); if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
@ -69,16 +64,23 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
return false; return false;
log.debug("remote logged in already. Establish state from session"); 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()); RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (securityContext != null) { if (securityContext != null) {
if (!deployment.getRealm().equals(securityContext.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this); securityContext.setCurrentRequestInfo(deployment, this);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
} }
GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
request.setUserPrincipal(principal);
request.setAuthType("KEYCLOAK");
((CatalinaRequestAuthenticator)authenticator).restoreRequest(); ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
return true; return true;
} }

View file

@ -66,12 +66,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
public void logout(Request request) throws ServletException { public void logout(Request request) throws ServletException {
KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName()); KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) { if (ksc != null) {
request.removeAttribute(KeycloakSecurityContext.class.getName());
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null); CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment); AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
tokenStore.logout(); tokenStore.logout();
request.removeAttribute(KeycloakSecurityContext.class.getName());
} }
super.logout(request); super.logout(request);
} }
@ -126,7 +126,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
// 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent) // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
// Outcome: adapter is left unconfigured // Outcome: adapter is left unconfigured
String configResolverClass = (String) context.getServletContext().getAttribute("keycloak.config.resolver"); String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) { if (configResolverClass != null) {
try { try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance(); KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();

View file

@ -71,7 +71,7 @@ public class JaxrsBearerTokenFilter implements ContainerRequestFilter {
try { try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realmPublicKey, realm); AccessToken token = RSATokenVerifier.verifyToken(tokenString, realmPublicKey, realm);
KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null, realm); KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null);
ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession); ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
final KeycloakPrincipal<KeycloakSecurityContext> principal = new KeycloakPrincipal<KeycloakSecurityContext>(token.getSubject(), skSession); final KeycloakPrincipal<KeycloakSecurityContext> principal = new KeycloakPrincipal<KeycloakSecurityContext>(token.getSubject(), skSession);

View file

@ -47,6 +47,12 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
if (authenticatedPrincipal != null) { if (authenticatedPrincipal != null) {
log.fine("remote logged in already. Establish state from cookie"); log.fine("remote logged in already. Establish state from cookie");
RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
if (!securityContext.getRealm().equals(deployment.getRealm())) {
log.fine("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this); securityContext.setCurrentRequestInfo(deployment, this);
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext); Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles, securityContext);
@ -95,11 +101,6 @@ public class CatalinaCookieTokenStore implements AdapterTokenStore {
RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext(); RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
if (!session.getRealm().equals(deployment.getRealm())) {
log.fine("Account from cookie is from a different realm than for the request.");
return null;
}
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal; if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
boolean success = session.refreshExpiredToken(false); boolean success = session.refreshExpiredToken(false);
if (success && session.isActive()) return principal; if (success && session.isActive()) return principal;

View file

@ -36,11 +36,6 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName()); RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (session == null) return; if (session == null) return;
if (!deployment.getRealm().equals(session.getRealm())) {
log.fine("Account from cookie is from a different realm than for the request.");
return;
}
// just in case session got serialized // just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this); if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
@ -67,16 +62,23 @@ public class CatalinaSessionTokenStore implements AdapterTokenStore {
if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null) if (request.getSessionInternal(false) == null || request.getSessionInternal().getPrincipal() == null)
return false; return false;
log.fine("remote logged in already. Establish state from session"); 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()); RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName());
if (securityContext != null) { if (securityContext != null) {
if (!deployment.getRealm().equals(securityContext.getRealm())) {
log.fine("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this); securityContext.setCurrentRequestInfo(deployment, this);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
} }
GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
request.setUserPrincipal(principal);
request.setAuthType("KEYCLOAK");
((CatalinaRequestAuthenticator)authenticator).restoreRequest(); ((CatalinaRequestAuthenticator)authenticator).restoreRequest();
return true; return true;
} }

View file

@ -73,12 +73,12 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
public void logout(Request request) throws ServletException { public void logout(Request request) throws ServletException {
KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName()); KeycloakSecurityContext ksc = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) { if (ksc != null) {
request.removeAttribute(KeycloakSecurityContext.class.getName());
CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null); CatalinaHttpFacade facade = new CatalinaHttpFacade(request, null);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment); AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
tokenStore.logout(); tokenStore.logout();
request.removeAttribute(KeycloakSecurityContext.class.getName());
} }
super.logout(request); super.logout(request);
} }
@ -103,7 +103,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif
// 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent) // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
// Outcome: adapter is left unconfigured // Outcome: adapter is left unconfigured
String configResolverClass = (String) context.getServletContext().getAttribute("keycloak.config.resolver"); String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) { if (configResolverClass != null) {
try { try {
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance(); KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();