From d873d5e71e9a36d5e474f40ab913178eb92d999d Mon Sep 17 00:00:00 2001 From: Frank Pavageau Date: Fri, 17 Mar 2017 16:50:15 +0100 Subject: [PATCH 1/2] Map values directly from the JSON nodes Not only is it more efficient without converting to an intermediate String, using JsonNode.toString() may not even produce valid JSON according to its Javadoc (ObjectMapper.writeValueAsString() should be used). --- .../security/jackson2/UnmodifiableSetDeserializer.java | 4 ++-- .../UsernamePasswordAuthenticationTokenDeserializer.java | 4 ++-- .../PreAuthenticatedAuthenticationTokenDeserializer.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetDeserializer.java index 73cba239f78..97256a8de00 100644 --- a/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetDeserializer.java +++ b/core/src/main/java/org/springframework/security/jackson2/UnmodifiableSetDeserializer.java @@ -50,10 +50,10 @@ public Set deserialize(JsonParser jp, DeserializationContext ctxt) throws IOExce Iterator nodeIterator = arrayNode.iterator(); while (nodeIterator.hasNext()) { JsonNode elementNode = nodeIterator.next(); - resultSet.add(mapper.readValue(elementNode.toString(), Object.class)); + resultSet.add(mapper.readValue(elementNode.traverse(mapper), Object.class)); } } else { - resultSet.add(mapper.readValue(node.toString(), Object.class)); + resultSet.add(mapper.readValue(node.traverse(mapper), Object.class)); } } return Collections.unmodifiableSet(resultSet); diff --git a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java index 450149e9bb2..4c86d5a83d4 100644 --- a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java +++ b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java @@ -62,13 +62,13 @@ public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, Deserializ JsonNode principalNode = readJsonNode(jsonNode, "principal"); Object principal = null; if(principalNode.isObject()) { - principal = mapper.readValue(principalNode.toString(), new TypeReference() {}); + principal = mapper.readValue(principalNode.traverse(mapper), new TypeReference() {}); } else { principal = principalNode.asText(); } Object credentials = readJsonNode(jsonNode, "credentials").asText(); List authorities = mapper.readValue( - readJsonNode(jsonNode, "authorities").toString(), new TypeReference>() { + readJsonNode(jsonNode, "authorities").traverse(mapper), new TypeReference>() { }); if (authenticated) { token = new UsernamePasswordAuthenticationToken(principal, credentials, authorities); diff --git a/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java b/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java index 48f19144363..0a8c0df9219 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java @@ -63,13 +63,13 @@ public PreAuthenticatedAuthenticationToken deserialize(JsonParser jp, Deserializ JsonNode principalNode = readJsonNode(jsonNode, "principal"); Object principal = null; if(principalNode.isObject()) { - principal = mapper.readValue(principalNode.toString(), new TypeReference() {}); + principal = mapper.readValue(principalNode.traverse(mapper), new TypeReference() {}); } else { principal = principalNode.asText(); } Object credentials = readJsonNode(jsonNode, "credentials").asText(); List authorities = mapper.readValue( - readJsonNode(jsonNode, "authorities").toString(), new TypeReference>() { + readJsonNode(jsonNode, "authorities").traverse(mapper), new TypeReference>() { }); if (authenticated) { token = new PreAuthenticatedAuthenticationToken(principal, credentials, authorities); From 10878d1217ecd8362f0515dc8ac62aa80ebc7231 Mon Sep 17 00:00:00 2001 From: Frank Pavageau Date: Fri, 17 Mar 2017 16:58:04 +0100 Subject: [PATCH 2/2] Deserialize the principal in a neutral way When the principal of the Authentication is an object, it is not necessarily an User: it could be another implementation of UserDetails, or even a completely unrelated type. Since the type of the object is serialized as a property and used by the deserialization anyway, there's no point in enforcing a stricter type. --- ...sswordAuthenticationTokenDeserializer.java | 3 +- ...PasswordAuthenticationTokenMixinTests.java | 48 +++++++++++++++++++ ...icatedAuthenticationTokenDeserializer.java | 3 +- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java index 4c86d5a83d4..1b6ef485ac5 100644 --- a/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java +++ b/core/src/main/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenDeserializer.java @@ -26,7 +26,6 @@ import com.fasterxml.jackson.databind.node.MissingNode; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; import java.io.IOException; import java.util.List; @@ -62,7 +61,7 @@ public UsernamePasswordAuthenticationToken deserialize(JsonParser jp, Deserializ JsonNode principalNode = readJsonNode(jsonNode, "principal"); Object principal = null; if(principalNode.isObject()) { - principal = mapper.readValue(principalNode.traverse(mapper), new TypeReference() {}); + principal = mapper.readValue(principalNode.traverse(mapper), Object.class); } else { principal = principalNode.asText(); } diff --git a/core/src/test/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixinTests.java b/core/src/test/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixinTests.java index 966c4b29c00..d2977d6cfe9 100644 --- a/core/src/test/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixinTests.java +++ b/core/src/test/java/org/springframework/security/jackson2/UsernamePasswordAuthenticationTokenMixinTests.java @@ -17,13 +17,16 @@ package org.springframework.security.jackson2; import java.io.IOException; +import java.util.ArrayList; +import com.fasterxml.jackson.annotation.JsonClassDescription; import com.fasterxml.jackson.core.JsonProcessingException; import org.json.JSONException; import org.junit.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; @@ -49,6 +52,20 @@ public class UsernamePasswordAuthenticationTokenMixinTests extends AbstractMixin public static final String AUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_JSON.replace( UserDeserializerTests.USER_JSON, "\"admin\""); // @formatter:on + // @formatter:off + private static final String NON_USER_PRINCIPAL_JSON = "{" + + "\"@class\": \"org.springframework.security.jackson2.UsernamePasswordAuthenticationTokenMixinTests$NonUserPrincipal\", " + + "\"username\": \"admin\"" + + "}"; + // @formatter:on + + // @formatter:off + private static final String AUTHENTICATED_NON_USER_PRINCIPAL_JSON = AUTHENTICATED_JSON + .replace(UserDeserializerTests.USER_JSON, NON_USER_PRINCIPAL_JSON) + .replaceAll(UserDeserializerTests.USER_PASSWORD, "null") + .replace(SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON, SimpleGrantedAuthorityMixinTests.NO_AUTHORITIES_ARRAYLIST_JSON); + // @formatter:on + // @formatter:off private static final String UNAUTHENTICATED_STRINGPRINCIPAL_JSON = AUTHENTICATED_STRINGPRINCIPAL_JSON .replace("\"authenticated\": true, ", "\"authenticated\": false, ") @@ -115,9 +132,40 @@ public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinAfterE JSONAssert.assertEquals(AUTHENTICATED_JSON.replaceAll(UserDeserializerTests.USER_PASSWORD, "null"), actualJson, true); } + @Test + public void serializeAuthenticatedUsernamePasswordAuthenticationTokenMixinWithNonUserPrincipalTest() throws JsonProcessingException, JSONException { + NonUserPrincipal principal = new NonUserPrincipal(); + principal.setUsername("admin"); + UsernamePasswordAuthenticationToken token = + new UsernamePasswordAuthenticationToken(principal, null, new ArrayList()); + String actualJson = mapper.writeValueAsString(token); + JSONAssert.assertEquals(AUTHENTICATED_NON_USER_PRINCIPAL_JSON, actualJson, true); + } + + @Test + public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenWithNonUserPrincipalTest() throws IOException { + UsernamePasswordAuthenticationToken token = mapper + .readValue(AUTHENTICATED_NON_USER_PRINCIPAL_JSON, UsernamePasswordAuthenticationToken.class); + assertThat(token).isNotNull(); + assertThat(token.getPrincipal()).isNotNull().isInstanceOf(NonUserPrincipal.class); + } + private UsernamePasswordAuthenticationToken createToken() { User user = createDefaultUser(); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()); return token; } + + @JsonClassDescription + public static class NonUserPrincipal { + private String username; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + } } diff --git a/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java b/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java index 0a8c0df9219..f58c2f0fb55 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java @@ -20,7 +20,6 @@ import java.util.List; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import com.fasterxml.jackson.core.JsonParser; @@ -63,7 +62,7 @@ public PreAuthenticatedAuthenticationToken deserialize(JsonParser jp, Deserializ JsonNode principalNode = readJsonNode(jsonNode, "principal"); Object principal = null; if(principalNode.isObject()) { - principal = mapper.readValue(principalNode.traverse(mapper), new TypeReference() {}); + principal = mapper.readValue(principalNode.traverse(mapper), Object.class); } else { principal = principalNode.asText(); }