From d7bcd41909e436a8090445af1ff134af9ad099be Mon Sep 17 00:00:00 2001 From: mposolda Date: Wed, 1 Oct 2014 19:08:41 +0200 Subject: [PATCH] Ensure that KeycloakUndertowAccount and referenced classes can be serialized in session --- .../java/org/keycloak/KeycloakPrincipal.java | 8 +-- .../org/keycloak/KeycloakSecurityContext.java | 39 ++++++++++- .../keycloak/representations/AccessToken.java | 3 +- .../org/keycloak/SkeletonKeyTokenTest.java | 67 +++++++++++++++++-- .../adapters/RequestAuthenticator.java | 12 ++-- .../as7/CatalinaRequestAuthenticator.java | 6 +- .../jaxrs/JaxrsBearerTokenFilter.java | 2 +- .../tomcat7/CatalinaRequestAuthenticator.java | 6 +- .../undertow/KeycloakUndertowAccount.java | 25 +++---- .../undertow/ServletRequestAuthenticator.java | 4 +- .../UndertowRequestAuthenticator.java | 10 +-- 11 files changed, 139 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/org/keycloak/KeycloakPrincipal.java b/core/src/main/java/org/keycloak/KeycloakPrincipal.java index ca05f791fb..e2819eab0a 100755 --- a/core/src/main/java/org/keycloak/KeycloakPrincipal.java +++ b/core/src/main/java/org/keycloak/KeycloakPrincipal.java @@ -7,16 +7,16 @@ import java.security.Principal; * @author Bill Burke * @version $Revision: 1 $ */ -public class KeycloakPrincipal implements Principal, Serializable { +public class KeycloakPrincipal implements Principal, Serializable { 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.context = context; } - public KeycloakSecurityContext getKeycloakSecurityContext() { + public T getKeycloakSecurityContext() { return context; } diff --git a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java index bea9d9f375..4658c68157 100755 --- a/core/src/main/java/org/keycloak/KeycloakSecurityContext.java +++ b/core/src/main/java/org/keycloak/KeycloakSecurityContext.java @@ -2,7 +2,12 @@ package org.keycloak; import org.keycloak.representations.AccessToken; 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; /** @@ -11,10 +16,12 @@ import java.io.Serializable; */ public class KeycloakSecurityContext implements Serializable { protected String tokenString; - protected AccessToken token; - protected IDToken idToken; protected String idTokenString; + // Don't store parsed tokens into HTTP session + protected transient AccessToken token; + protected transient IDToken idToken; + public KeycloakSecurityContext() { } @@ -40,4 +47,32 @@ public class KeycloakSecurityContext implements Serializable { public String getIdTokenString() { 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 parseToken(String encoded, Class 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); + } + + } diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java index 262d07dc1f..a9eaf3e7f3 100755 --- a/core/src/main/java/org/keycloak/representations/AccessToken.java +++ b/core/src/main/java/org/keycloak/representations/AccessToken.java @@ -3,6 +3,7 @@ package org.keycloak.representations; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonProperty; +import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -13,7 +14,7 @@ import java.util.Set; * @version $Revision: 1 $ */ public class AccessToken extends IDToken { - public static class Access { + public static class Access implements Serializable { @JsonProperty("roles") protected Set roles; @JsonProperty("verify_caller") diff --git a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java index cd948b8be5..f3a89b6d1a 100755 --- a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java +++ b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java @@ -1,13 +1,18 @@ package org.keycloak; -import junit.framework.Assert; +import org.junit.Assert; import org.junit.Test; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; 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.KeyPairGenerator; @@ -18,10 +23,7 @@ import java.security.KeyPairGenerator; public class SkeletonKeyTokenTest { @Test public void testToken() throws Exception { - AccessToken token = new AccessToken(); - token.id("111"); - token.addAccess("foo").addRole("admin"); - token.addAccess("bar").addRole("user"); + AccessToken token = createSimpleToken(); String json = JsonSerialization.writeValueAsString(token); token = JsonSerialization.readValue(json, AccessToken.class); @@ -34,7 +36,7 @@ public class SkeletonKeyTokenTest { @Test public void testRSA() throws Exception { - AccessToken token = new AccessToken(); + AccessToken token = createSimpleToken(); token.id("111"); token.addAccess("foo").addRole("admin"); token.addAccess("bar").addRole("user"); @@ -51,4 +53,57 @@ public class SkeletonKeyTokenTest { Assert.assertEquals("111", token.getId()); 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; + } } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java index 7073987c91..3cf2c91f55 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java @@ -104,18 +104,18 @@ public abstract class RequestAuthenticator { protected void completeAuthentication(OAuthRequestAuthenticator oauth) { RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken()); - final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), session); - completeOAuthAuthentication(principal, session); + final KeycloakPrincipal principal = new KeycloakPrincipal(oauth.getToken().getSubject(), session); + completeOAuthAuthentication(principal); } - protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); - protected abstract void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); + protected abstract void completeOAuthAuthentication(KeycloakPrincipal principal); + protected abstract void completeBearerAuthentication(KeycloakPrincipal principal); protected abstract boolean isCached(); protected void completeAuthentication(BearerTokenRequestAuthenticator bearer) { RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, bearer.getTokenString(), bearer.getToken(), null, null, null); - final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), session); - completeBearerAuthentication(principal, session); + final KeycloakPrincipal principal = new KeycloakPrincipal(bearer.getToken().getSubject(), session); + completeBearerAuthentication(principal); } } diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java index 4be97da6d5..aca4db7154 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/CatalinaRequestAuthenticator.java @@ -53,7 +53,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator { } @Override - protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) { + protected void completeOAuthAuthentication(KeycloakPrincipal skp) { + RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext(); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); Set roles = getRolesFromToken(securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext); @@ -67,7 +68,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator { } @Override - protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) { + protected void completeBearerAuthentication(KeycloakPrincipal principal) { + RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); Set roles = getRolesFromToken(securityContext); for (String role : roles) { log.info("Bearer role: " + role); diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilter.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilter.java index 6ba99842a6..5d700c1a8b 100755 --- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilter.java +++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilter.java @@ -74,7 +74,7 @@ public class JaxrsBearerTokenFilter implements ContainerRequestFilter { KeycloakSecurityContext skSession = new KeycloakSecurityContext(tokenString, token, null, null); ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession); - final KeycloakPrincipal principal = new KeycloakPrincipal(token.getSubject(), skSession); + final KeycloakPrincipal principal = new KeycloakPrincipal(token.getSubject(), skSession); final boolean isSecure = securityContext.isSecure(); final AccessToken.Access access; if (resourceName != null) { diff --git a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java index 4738443ce6..6a05be844b 100755 --- a/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java +++ b/integration/tomcat7/adapter/src/main/java/org/keycloak/adapters/tomcat7/CatalinaRequestAuthenticator.java @@ -53,7 +53,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator { } @Override - protected void completeOAuthAuthentication(KeycloakPrincipal skp, RefreshableKeycloakSecurityContext securityContext) { + protected void completeOAuthAuthentication(KeycloakPrincipal skp) { + RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext(); request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext); Set roles = getRolesFromToken(securityContext); GenericPrincipal principal = new CatalinaSecurityContextHelper().createPrincipal(request.getContext().getRealm(), skp, roles, securityContext); @@ -67,7 +68,8 @@ public class CatalinaRequestAuthenticator extends RequestAuthenticator { } @Override - protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext securityContext) { + protected void completeBearerAuthentication(KeycloakPrincipal principal) { + RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext(); Set roles = getRolesFromToken(securityContext); for (String role : roles) { log.info("Bearer role: " + role); diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java index 02680f4bc4..58cbb023d5 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java @@ -35,18 +35,17 @@ import java.util.Set; */ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakAccount { protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class); - protected RefreshableKeycloakSecurityContext session; - protected KeycloakPrincipal principal; + protected KeycloakPrincipal principal; protected Set accountRoles; - public KeycloakUndertowAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session, KeycloakDeployment deployment) { + public KeycloakUndertowAccount(KeycloakPrincipal principal) { this.principal = principal; - this.session = session; - setRoles(session.getToken()); + setRoles(principal.getKeycloakSecurityContext().getToken()); } protected void setRoles(AccessToken accessToken) { Set roles = null; + RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext(); if (session.getDeployment().isUseResourceRoleMappings()) { if (log.isTraceEnabled()) { log.trace("useResourceRoleMappings"); @@ -61,12 +60,13 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA if (access != null) roles = access.getRoles(); } if (roles == null) roles = Collections.emptySet(); - /* - log.info("Setting roles: "); - for (String role : roles) { - log.info(" role: " + role); + if (log.isTraceEnabled()) { + log.trace("Setting roles: "); + for (String role : roles) { + log.trace(" role: " + role); + } } - */ + this.accountRoles = roles; } @@ -82,15 +82,16 @@ public class KeycloakUndertowAccount implements Account, Serializable, KeycloakA @Override public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() { - return session; + return principal.getKeycloakSecurityContext(); } public void setDeployment(KeycloakDeployment deployment) { - session.setDeployment(deployment); + principal.getKeycloakSecurityContext().setDeployment(deployment); } public boolean isActive() { // this object may have been serialized, so we need to reset realm config/metadata + RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext(); if (session.isActive()) { log.debug("session is active"); return true; diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java index a8365cd20d..c46f477d31 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java @@ -88,7 +88,7 @@ public class ServletRequestAuthenticator extends UndertowRequestAuthenticator { } @Override - protected KeycloakUndertowAccount createAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { - return new KeycloakUndertowAccount(principal, session, deployment); + protected KeycloakUndertowAccount createAccount(KeycloakPrincipal principal) { + return new KeycloakUndertowAccount(principal); } } diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java index 67a7b7a12a..79435aef2e 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java @@ -63,8 +63,8 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator } @Override - protected void completeOAuthAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { - KeycloakUndertowAccount account = createAccount(principal, session); + protected void completeOAuthAuthentication(KeycloakPrincipal principal) { + KeycloakUndertowAccount account = createAccount(principal); securityContext.authenticationComplete(account, "KEYCLOAK", false); propagateKeycloakContext(account); login(account); @@ -80,8 +80,8 @@ public abstract class UndertowRequestAuthenticator extends RequestAuthenticator @Override - protected void completeBearerAuthentication(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session) { - KeycloakUndertowAccount account = createAccount(principal, session); + protected void completeBearerAuthentication(KeycloakPrincipal principal) { + KeycloakUndertowAccount account = createAccount(principal); securityContext.authenticationComplete(account, "KEYCLOAK", false); 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 * @return The account */ - protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal principal, RefreshableKeycloakSecurityContext session); + protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal principal); }