Ensure that KeycloakUndertowAccount and referenced classes can be serialized in session

This commit is contained in:
mposolda 2014-10-01 19:08:41 +02:00
parent eb920b4993
commit d7bcd41909
11 changed files with 139 additions and 43 deletions

View file

@ -7,16 +7,16 @@ import java.security.Principal;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeycloakPrincipal implements Principal, Serializable { public class KeycloakPrincipal<T extends KeycloakSecurityContext> implements Principal, Serializable {
protected final String name; protected final String name;
protected final KeycloakSecurityContext context; protected final T context;
public KeycloakPrincipal(String name, KeycloakSecurityContext context) { public KeycloakPrincipal(String name, T context) {
this.name = name; this.name = name;
this.context = context; this.context = context;
} }
public KeycloakSecurityContext getKeycloakSecurityContext() { public T getKeycloakSecurityContext() {
return context; return context;
} }

View file

@ -2,7 +2,12 @@ package org.keycloak;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.util.Base64Url;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
/** /**
@ -11,10 +16,12 @@ import java.io.Serializable;
*/ */
public class KeycloakSecurityContext implements Serializable { public class KeycloakSecurityContext implements Serializable {
protected String tokenString; protected String tokenString;
protected AccessToken token;
protected IDToken idToken;
protected String idTokenString; protected String idTokenString;
// Don't store parsed tokens into HTTP session
protected transient AccessToken token;
protected transient IDToken idToken;
public KeycloakSecurityContext() { public KeycloakSecurityContext() {
} }
@ -40,4 +47,32 @@ public class KeycloakSecurityContext implements Serializable {
public String getIdTokenString() { public String getIdTokenString() {
return idTokenString; return idTokenString;
} }
// SERIALIZATION
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
token = parseToken(tokenString, AccessToken.class);
idToken = parseToken(idTokenString, IDToken.class);
}
// Just decode token without any verifications
private <T> T parseToken(String encoded, Class<T> clazz) throws IOException {
if (encoded == null)
return null;
String[] parts = encoded.split("\\.");
if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
byte[] bytes = Base64Url.decode(parts[1]);
return JsonSerialization.readValue(bytes, clazz);
}
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.representations;
import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonProperty;
import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -13,7 +14,7 @@ import java.util.Set;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AccessToken extends IDToken { public class AccessToken extends IDToken {
public static class Access { public static class Access implements Serializable {
@JsonProperty("roles") @JsonProperty("roles")
protected Set<String> roles; protected Set<String> roles;
@JsonProperty("verify_caller") @JsonProperty("verify_caller")

View file

@ -1,13 +1,18 @@
package org.keycloak; package org.keycloak;
import junit.framework.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -18,10 +23,7 @@ import java.security.KeyPairGenerator;
public class SkeletonKeyTokenTest { public class SkeletonKeyTokenTest {
@Test @Test
public void testToken() throws Exception { public void testToken() throws Exception {
AccessToken token = new AccessToken(); AccessToken token = createSimpleToken();
token.id("111");
token.addAccess("foo").addRole("admin");
token.addAccess("bar").addRole("user");
String json = JsonSerialization.writeValueAsString(token); String json = JsonSerialization.writeValueAsString(token);
token = JsonSerialization.readValue(json, AccessToken.class); token = JsonSerialization.readValue(json, AccessToken.class);
@ -34,7 +36,7 @@ public class SkeletonKeyTokenTest {
@Test @Test
public void testRSA() throws Exception { public void testRSA() throws Exception {
AccessToken token = new AccessToken(); AccessToken token = createSimpleToken();
token.id("111"); token.id("111");
token.addAccess("foo").addRole("admin"); token.addAccess("foo").addRole("admin");
token.addAccess("bar").addRole("user"); token.addAccess("bar").addRole("user");
@ -51,4 +53,57 @@ public class SkeletonKeyTokenTest {
Assert.assertEquals("111", token.getId()); Assert.assertEquals("111", token.getId());
Assert.assertTrue(RSAProvider.verify(input, keyPair.getPublic())); Assert.assertTrue(RSAProvider.verify(input, keyPair.getPublic()));
} }
@Test
public void testSerialization() throws Exception {
AccessToken token = createSimpleToken();
IDToken idToken = new IDToken();
idToken.setEmail("joe@email.cz");
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
String encoded = new JWSBuilder()
.jsonContent(token)
.rsa256(keyPair.getPrivate());
String encodedIdToken = new JWSBuilder()
.jsonContent(idToken)
.rsa256(keyPair.getPrivate());
KeycloakSecurityContext ctx = new KeycloakSecurityContext(encoded, token, encodedIdToken, idToken);
KeycloakPrincipal principal = new KeycloakPrincipal("joe", ctx);
// Serialize
ByteArrayOutputStream bso = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bso);
oos.writeObject(principal);
oos.close();
// Deserialize
byte[] bytes = bso.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
principal = (KeycloakPrincipal)ois.readObject();
ctx = principal.getKeycloakSecurityContext();
token = ctx.getToken();
idToken = ctx.getIdToken();
System.out.println("Size of serialized principal: " + bytes.length);
Assert.assertEquals(encoded, ctx.getTokenString());
Assert.assertEquals(encodedIdToken, ctx.getIdTokenString());
Assert.assertEquals("111", token.getId());
Assert.assertEquals("111", token.getId());
Assert.assertTrue(token.getResourceAccess("foo").isUserInRole("admin"));
Assert.assertTrue(token.getResourceAccess("bar").isUserInRole("user"));
Assert.assertEquals("joe@email.cz", idToken.getEmail());
ois.close();
}
private AccessToken createSimpleToken() {
AccessToken token = new AccessToken();
token.id("111");
token.addAccess("foo").addRole("admin");
token.addAccess("bar").addRole("user");
return token;
}
} }

View file

@ -104,18 +104,18 @@ public abstract class RequestAuthenticator {
protected void completeAuthentication(OAuthRequestAuthenticator oauth) { 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, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(oauth.getToken().getSubject(), session);
completeOAuthAuthentication(principal, session); completeOAuthAuthentication(principal);
} }
protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract boolean isCached(); protected abstract boolean isCached();
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) { protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null);
final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(bearer.getToken().getSubject(), session);
completeBearerAuthentication(principal, session); completeBearerAuthentication(principal);
} }
} }

View file

@ -53,7 +53,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
} }
@Override @Override
protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) { protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
@ -67,7 +68,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
} }
@Override @Override
protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) { protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
for (String role : roles) { for (String role : roles) {
log.info("Bearer role: " + role); log.info("Bearer role: " + role);

View file

@ -74,7 +74,7 @@ public class JaxrsBearerTokenFilter implements ContainerRequestFilter {
KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null); KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null);
ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession); ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
final KeycloakPrincipal principal = new KeycloakPrincipal(token.getSubject(), skSession); final KeycloakPrincipal<KeycloakSecurityContext> principal = new KeycloakPrincipal<KeycloakSecurityContext>(token.getSubject(), skSession);
final boolean isSecure = securityContext.isSecure(); final boolean isSecure = securityContext.isSecure();
final AccessToken.Access access; final AccessToken.Access access;
if (resourceName != null) { if (resourceName != null) {

View file

@ -53,7 +53,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
} }
@Override @Override
protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) { protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext);
@ -67,7 +68,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator {
} }
@Override @Override
protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) { protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = getRolesFromToken(securityContext); Set<String> roles = getRolesFromToken(securityContext);
for (String role : roles) { for (String role : roles) {
log.info("Bearer role: " + role); log.info("Bearer role: " + role);

View file

@ -35,18 +35,17 @@ import java.util.Set;
*/ */
public class KeycloakUndertowAccount implements Account, Serializable, KeycloakAccount { public class KeycloakUndertowAccount implements Account, Serializable, KeycloakAccount {
protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class); protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
protected RefreshableKeycloakSecurityContext session; protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
protected KeycloakPrincipal principal;
protected Set<String> accountRoles; protected Set<String> accountRoles;
public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session, KeycloakDeployment deployment) { public KeycloakUndertowAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
this.principal = principal; this.principal = principal;
this.session = session; setRoles(principal.getKeycloakSecurityContext().getToken());
setRoles(session.getToken());
} }
protected void setRoles(AccessToken accessToken) { protected void setRoles(AccessToken accessToken) {
Set<String> roles = null; Set<String> roles = null;
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.getDeployment().isUseResourceRoleMappings()) { if (session.getDeployment().isUseResourceRoleMappings()) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("useResourceRoleMappings"); log.trace("useResourceRoleMappings");
@ -61,12 +60,13 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
if (access != null) roles = access.getRoles(); if (access != null) roles = access.getRoles();
} }
if (roles == null) roles = Collections.emptySet(); if (roles == null) roles = Collections.emptySet();
/* if (log.isTraceEnabled()) {
log.info("Setting roles: "); log.trace("Setting roles: ");
for (String role : roles) { for (String role : roles) {
log.info(" role: " + role); log.trace(" role: " + role);
} }
*/ }
this.accountRoles = roles; this.accountRoles = roles;
} }
@ -82,15 +82,16 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA
@Override @Override
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() { public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
return session; return principal.getKeycloakSecurityContext();
} }
public void setDeployment(KeycloakDeployment deployment) { public void setDeployment(KeycloakDeployment deployment) {
session.setDeployment(deployment); principal.getKeycloakSecurityContext().setDeployment(deployment);
} }
public boolean isActive() { public boolean isActive() {
// this object may have been serialized, so we need to reset realm config/metadata // this object may have been serialized, so we need to reset realm config/metadata
RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
if (session.isActive()) { if (session.isActive()) {
log.debug("session is active"); log.debug("session is active");
return true; return true;

View file

@ -88,7 +88,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator {
} }
@Override @Override
protected KeycloakUndertowAccount createAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
return new KeycloakUndertowAccount(principal, session, deployment); return new KeycloakUndertowAccount(principal);
} }
} }

View file

@ -63,8 +63,8 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
} }
@Override @Override
protected void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
KeycloakUndertowAccount account = createAccount(principal, session); KeycloakUndertowAccount account = createAccount(principal);
securityContext.authenticationComplete(account, "KEYCLOAK", false); securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext(account); propagateKeycloakContext(account);
login(account); login(account);
@ -80,8 +80,8 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
@Override @Override
protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
KeycloakUndertowAccount account = createAccount(principal, session); KeycloakUndertowAccount account = createAccount(principal);
securityContext.authenticationComplete(account, "KEYCLOAK", false); securityContext.authenticationComplete(account, "KEYCLOAK", false);
propagateKeycloakContext(account); propagateKeycloakContext(account);
} }
@ -114,5 +114,5 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator
* Subclasses need to be able to create their own version of the KeycloakUndertowAccount * Subclasses need to be able to create their own version of the KeycloakUndertowAccount
* @return The account * @return The account
*/ */
protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
} }