Selaa lähdekoodia

Add Jackson2 Support for PreAuthenticatedAuthenticationToken

Fixes gh-4120
Rob Winch 8 vuotta sitten
vanhempi
commit
697daeab7c

+ 86 - 0
web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenDeserializer.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.security.web.jackson2;
+
+import java.io.IOException;
+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;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.MissingNode;
+
+/**
+ * Custom deserializer for {@link PreAuthenticatedAuthenticationToken}. At the time of deserialization
+ * it will invoke suitable constructor depending on the value of <b>authenticated</b> property.
+ * It will ensure that the token's state must not change.
+ * <p>
+ * This deserializer is already registered with {@link PreAuthenticatedAuthenticationTokenMixin} but
+ * you can also registered it with your own mixin class.
+ *
+ * @author Jitendra Singh
+ * @see PreAuthenticatedAuthenticationTokenMixin
+ * @since 4.2
+ */
+class PreAuthenticatedAuthenticationTokenDeserializer extends JsonDeserializer<PreAuthenticatedAuthenticationToken> {
+
+	/**
+	 * This method construct {@link PreAuthenticatedAuthenticationToken} object from serialized json.
+	 * @param jp the JsonParser
+	 * @param ctxt the DeserializationContext
+	 * @return the user
+	 * @throws IOException if a exception during IO occurs
+	 * @throws JsonProcessingException if an error during JSON processing occurs
+	 */
+	@Override
+	public PreAuthenticatedAuthenticationToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+		PreAuthenticatedAuthenticationToken token = null;
+		ObjectMapper mapper = (ObjectMapper) jp.getCodec();
+		JsonNode jsonNode = mapper.readTree(jp);
+		Boolean authenticated = readJsonNode(jsonNode, "authenticated").asBoolean();
+		JsonNode principalNode = readJsonNode(jsonNode, "principal");
+		Object principal = null;
+		if(principalNode.isObject()) {
+			principal = mapper.readValue(principalNode.toString(), new TypeReference<User>() {});
+		} else {
+			principal = principalNode.asText();
+		}
+		Object credentials = readJsonNode(jsonNode, "credentials").asText();
+		List<GrantedAuthority> authorities = mapper.readValue(
+				readJsonNode(jsonNode, "authorities").toString(), new TypeReference<List<GrantedAuthority>>() {
+		});
+		if (authenticated) {
+			token = new PreAuthenticatedAuthenticationToken(principal, credentials, authorities);
+		} else {
+			token = new PreAuthenticatedAuthenticationToken(principal, credentials);
+		}
+		token.setDetails(readJsonNode(jsonNode, "details"));
+		return token;
+	}
+
+	private JsonNode readJsonNode(JsonNode jsonNode, String field) {
+		return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
+	}
+}

+ 52 - 0
web/src/main/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenMixin.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.security.web.jackson2;
+
+import org.springframework.security.jackson2.SecurityJackson2Modules;
+import org.springframework.security.jackson2.SimpleGrantedAuthorityMixin;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+/**
+ * This mixin class is used to serialize / deserialize
+ * {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken}. This class register
+ * a custom deserializer {@link PreAuthenticatedAuthenticationTokenDeserializer}.
+ *
+ * In order to use this mixin you'll need to add 3 more mixin classes.
+ * <ol>
+ *     <li>{@link UnmodifiableSetMixin}</li>
+ *     <li>{@link SimpleGrantedAuthorityMixin}</li>
+ *     <li>{@link UserMixin}</li>
+ * </ol>
+ *
+ * <pre>
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new CoreJackson2Module());
+ * </pre>
+ * @author Jitendra Singh
+ * @see Webackson2Module
+ * @see SecurityJackson2Modules
+ * @since 4.2
+ */
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
+		isGetterVisibility = JsonAutoDetect.Visibility.NONE)
+@JsonDeserialize(using = PreAuthenticatedAuthenticationTokenDeserializer.class)
+abstract class PreAuthenticatedAuthenticationTokenMixin {
+}

+ 2 - 0
web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java

@@ -20,6 +20,7 @@ import javax.servlet.http.Cookie;
 
 import org.springframework.security.jackson2.SecurityJackson2Modules;
 import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
 import org.springframework.security.web.csrf.DefaultCsrfToken;
 import org.springframework.security.web.savedrequest.DefaultSavedRequest;
 import org.springframework.security.web.savedrequest.SavedCookie;
@@ -58,5 +59,6 @@ public class WebJackson2Module extends SimpleModule {
 		context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
 		context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
 		context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
+		context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
 	}
 }

+ 68 - 0
web/src/test/java/org/springframework/security/web/jackson2/PreAuthenticatedAuthenticationTokenMixinTests.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015-2016 the original author or authors.
+ *
+ * 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.springframework.security.web.jackson2;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.json.JSONException;
+import org.junit.Before;
+import org.junit.Test;
+import org.skyscreamer.jsonassert.JSONAssert;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.jackson2.SimpleGrantedAuthorityMixinTests;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+/**
+ * @author Rob Winch
+ * @since 4.2
+ */
+public class PreAuthenticatedAuthenticationTokenMixinTests extends AbstractMixinTests {
+	// @formatter:off
+	private static final String PREAUTH_JSON = "{"
+		+ "\"@class\": \"org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken\","
+		+ "\"principal\": \"principal\", "
+		+ "\"credentials\": \"credentials\", "
+		+ "\"authenticated\": true, "
+		+ "\"details\": null, "
+		+ "\"authorities\": "+ SimpleGrantedAuthorityMixinTests.AUTHORITIES_ARRAYLIST_JSON
+	+ "}";
+	// @formatter:on
+
+	PreAuthenticatedAuthenticationToken expected;
+
+	@Before
+	public void setupExpected() {
+		expected = new PreAuthenticatedAuthenticationToken("principal", "credentials", AuthorityUtils.createAuthorityList("ROLE_USER"));
+	}
+
+	@Test
+	public void serializeWhenPrincipalCredentialsAuthoritiesThenSuccess() throws JsonProcessingException, JSONException {
+		String serializedJson = mapper.writeValueAsString(expected);
+		JSONAssert.assertEquals(PREAUTH_JSON, serializedJson, true);
+	}
+
+	@Test
+	public void deserializeAuthenticatedUsernamePasswordAuthenticationTokenMixinTest() throws Exception{
+		PreAuthenticatedAuthenticationToken deserialized = mapper
+				.readValue(PREAUTH_JSON, PreAuthenticatedAuthenticationToken.class);
+		assertThat(deserialized).isNotNull();
+		assertThat(deserialized.isAuthenticated()).isTrue();
+		assertThat(deserialized.getAuthorities()).isEqualTo(expected.getAuthorities());
+	}
+}