KEYCLOAK-14871 Whitelist RefreshableKeycloakSecurityContext for KeycloakPrincipal serialization
This commit is contained in:
parent
086f7b4696
commit
d266165f63
8 changed files with 341 additions and 0 deletions
|
@ -1,8 +1,21 @@
|
|||
package org.keycloak.adapters;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
@ -39,4 +52,63 @@ public class RefreshableKeycloakSecurityContextTest {
|
|||
token.issuedAt(5000);
|
||||
assertTrue(sut.isActive());
|
||||
}
|
||||
|
||||
private AccessToken createSimpleToken() {
|
||||
AccessToken token = new AccessToken();
|
||||
token.id("111");
|
||||
token.issuer("http://localhost:8080/auth/acme");
|
||||
token.addAccess("foo").addRole("admin");
|
||||
token.addAccess("bar").addRole("user");
|
||||
return token;
|
||||
}
|
||||
|
||||
@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());
|
||||
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setNotBefore(5000);
|
||||
|
||||
KeycloakSecurityContext ctx = new RefreshableKeycloakSecurityContext(keycloakDeployment,null, encoded, token,encodedIdToken, null, null);
|
||||
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());
|
||||
Assert.assertEquals("acme", ctx.getRealm());
|
||||
ois.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ public class KeycloakPrincipal<T extends KeycloakSecurityContext> implements Pri
|
|||
DelegatingSerializationFilter.builder()
|
||||
.addAllowedClass(KeycloakPrincipal.class)
|
||||
.addAllowedClass(KeycloakSecurityContext.class)
|
||||
.addAllowedPattern("org.keycloak.adapters.RefreshableKeycloakSecurityContext")
|
||||
.setFilter(in);
|
||||
|
||||
in.defaultReadObject();
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.adapter.servlet;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* @author mhajas
|
||||
*/
|
||||
@WebServlet("/serialization-servlet")
|
||||
public class SerializationServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
PrintWriter pw = resp.getWriter();
|
||||
// Serialize
|
||||
ByteArrayOutputStream bso = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(bso);
|
||||
oos.writeObject(req.getUserPrincipal());
|
||||
oos.close();
|
||||
|
||||
// Deserialize
|
||||
byte[] bytes = bso.toByteArray();
|
||||
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(bis) {
|
||||
@Override
|
||||
public Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName(desc.getName(), true, SerializationServlet.class.getClassLoader());
|
||||
} catch (Exception e) { }
|
||||
|
||||
// Fall back (e.g. for primClasses)
|
||||
return super.resolveClass(desc);
|
||||
}
|
||||
};
|
||||
|
||||
KeycloakPrincipal principal;
|
||||
try {
|
||||
principal = (KeycloakPrincipal) ois.readObject();
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
pw.write("Deserialization failed");
|
||||
return;
|
||||
}
|
||||
|
||||
KeycloakSecurityContext ctx = principal.getKeycloakSecurityContext();
|
||||
if (!(ctx instanceof RefreshableKeycloakSecurityContext)) {
|
||||
pw.write("Context was not instance of RefreshableKeycloakSecurityContext");
|
||||
}
|
||||
|
||||
pw.write("Serialization/Deserialization was successful");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.adapter.page;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
|
||||
import org.jboss.arquillian.test.api.ArquillianResource;
|
||||
import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
|
||||
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tkyjovsk
|
||||
*/
|
||||
public class SerializationServletPage extends AbstractPageWithInjectedUrl {
|
||||
|
||||
public static final String DEPLOYMENT_NAME = "serialization-servlet";
|
||||
|
||||
@ArquillianResource
|
||||
@OperateOnDeployment(DEPLOYMENT_NAME)
|
||||
private URL url;
|
||||
|
||||
@Override
|
||||
public URL getInjectedUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public URI logout() {
|
||||
return getUriBuilder().clone().path("logout").build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.keycloak.testsuite.adapter.servlet;
|
||||
|
||||
import org.jboss.arquillian.container.test.api.Deployment;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.jboss.shrinkwrap.api.spec.WebArchive;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
|
||||
import org.keycloak.testsuite.adapter.page.SerializationServletPage;
|
||||
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
|
||||
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
|
||||
@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
|
||||
// The purpose of this class is to test KeycloakPrincipal serialization on different app-server-jdks
|
||||
public class KeycloakPrincipalSerializationTest extends AbstractServletsAdapterTest {
|
||||
@Page
|
||||
protected SerializationServletPage serializationServlet;
|
||||
|
||||
@Deployment(name = SerializationServletPage.DEPLOYMENT_NAME)
|
||||
protected static WebArchive serializationServlet() {
|
||||
return servletDeployment(SerializationServletPage.DEPLOYMENT_NAME, SerializationServlet.class, ErrorServlet.class, ServletTestUtils.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeycloakPrincipalSerialization() {
|
||||
serializationServlet.navigateTo();
|
||||
testRealmLoginPage.form().login("bburke@redhat.com", "password");
|
||||
|
||||
assertThat(driver.getPageSource(), containsString("Serialization/Deserialization was successful"));
|
||||
assertThat(driver.getPageSource(), not(containsString("Context was not instance of RefreshableKeycloakSecurityContext")));
|
||||
assertThat(driver.getPageSource(), not(containsString("Deserialization failed")));
|
||||
}
|
||||
}
|
|
@ -151,6 +151,17 @@
|
|||
"secret": "password",
|
||||
"directAccessGrantsEnabled": true
|
||||
},
|
||||
{
|
||||
"clientId": "serialization-servlet",
|
||||
"enabled": true,
|
||||
"adminUrl": "/serialization-servlet",
|
||||
"baseUrl": "/serialization-servlet",
|
||||
"redirectUris": [
|
||||
"/serialization-servlet/*"
|
||||
],
|
||||
"secret": "password",
|
||||
"directAccessGrantsEnabled": true
|
||||
},
|
||||
{
|
||||
"clientId": "customer-portal-subsystem",
|
||||
"enabled": true,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"realm": "demo",
|
||||
"resource": "serialization-servlet",
|
||||
"auth-server-url": "http://localhost:8180/auth",
|
||||
"ssl-required" : "external",
|
||||
"expose-token": true,
|
||||
"credentials": {
|
||||
"secret": "password"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0">
|
||||
|
||||
<module-name>serialization-servlet</module-name>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<servlet-class>org.keycloak.testsuite.adapter.servlet.SerializationServlet</servlet-class>
|
||||
</servlet>
|
||||
<servlet>
|
||||
<servlet-name>Error Servlet</servlet-name>
|
||||
<servlet-class>org.keycloak.testsuite.adapter.servlet.ErrorServlet</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Servlet</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Error Servlet</servlet-name>
|
||||
<url-pattern>/error.html</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Users</web-resource-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>user</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>Errors</web-resource-name>
|
||||
<url-pattern>/error.html</url-pattern>
|
||||
</web-resource-collection>
|
||||
</security-constraint>
|
||||
|
||||
<login-config>
|
||||
<auth-method>KEYCLOAK</auth-method>
|
||||
<realm-name>demo</realm-name>
|
||||
<form-login-config>
|
||||
<form-login-page>/error.html</form-login-page>
|
||||
<form-error-page>/error.html</form-error-page>
|
||||
</form-login-config>
|
||||
</login-config>
|
||||
|
||||
<security-role>
|
||||
<role-name>admin</role-name>
|
||||
</security-role>
|
||||
<security-role>
|
||||
<role-name>user</role-name>
|
||||
</security-role>
|
||||
</web-app>
|
Loading…
Reference in a new issue