diff --git a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java index 21f03b5d56f..a9138ef5eb7 100644 --- a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java +++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java @@ -57,6 +57,7 @@ * mapper.registerModule(new CoreJackson2Module()); * mapper.registerModule(new CasJackson2Module()); * mapper.registerModule(new WebJackson2Module()); + * mapper.registerModule(new WebServerJackson2Module()); * * * @author Jitendra Singh. @@ -68,7 +69,8 @@ public final class SecurityJackson2Modules { private static final List securityJackson2ModuleClasses = Arrays.asList( "org.springframework.security.jackson2.CoreJackson2Module", "org.springframework.security.cas.jackson2.CasJackson2Module", - "org.springframework.security.web.jackson2.WebJackson2Module" + "org.springframework.security.web.jackson2.WebJackson2Module", + "org.springframework.security.web.server.jackson2.WebServerJackson2Module" ); private SecurityJackson2Modules() { diff --git a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java index de9adaf2079..470363395d8 100644 --- a/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java +++ b/web/src/main/java/org/springframework/security/web/jackson2/WebJackson2Module.java @@ -26,14 +26,15 @@ import org.springframework.security.web.savedrequest.SavedCookie; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.util.ClassUtils; /** * Jackson module for spring-security-web. This module register {@link CookieMixin}, * {@link DefaultCsrfTokenMixin}, {@link DefaultSavedRequestMixin} and {@link WebAuthenticationDetailsMixin}. If no * default typing enabled by default then it'll enable it because typing info is needed to properly serialize/deserialize objects. * In order to use this module just add this module into your ObjectMapper configuration. + * If there is no javax.servlet package in classpath all mixins that depends on it will be skipped. * *
  *     ObjectMapper mapper = new ObjectMapper();
@@ -53,12 +54,14 @@ public WebJackson2Module() {
 
 	@Override
 	public void setupModule(SetupContext context) {
-		SecurityJackson2Modules.enableDefaultTyping((ObjectMapper) context.getOwner());
-		context.setMixInAnnotations(Cookie.class, CookieMixin.class);
-		context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
+		SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
+		if (ClassUtils.isPresent("javax.servlet.http.Cookie", this.getClass().getClassLoader())) {
+			context.setMixInAnnotations(Cookie.class, CookieMixin.class);
+			context.setMixInAnnotations(SavedCookie.class, SavedCookieMixin.class);
+			context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
+			context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
+		}
 		context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfTokenMixin.class);
-		context.setMixInAnnotations(DefaultSavedRequest.class, DefaultSavedRequestMixin.class);
-		context.setMixInAnnotations(WebAuthenticationDetails.class, WebAuthenticationDetailsMixin.class);
 		context.setMixInAnnotations(PreAuthenticatedAuthenticationToken.class, PreAuthenticatedAuthenticationTokenMixin.class);
 	}
 }
diff --git a/web/src/main/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixin.java b/web/src/main/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixin.java
new file mode 100644
index 00000000000..496900a5037
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixin.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015-2018 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.server.jackson2;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+
+/**
+ * Jackson mixin class to serialize/deserialize {@link org.springframework.security.web.server.csrf.DefaultCsrfToken}
+ * serialization support.
+ *
+ * 
+ * 		ObjectMapper mapper = new ObjectMapper();
+ * 		mapper.registerModule(new WebServerJackson2Module());
+ * 
+ * + * @author Boris Finkelshteyn + * @see WebServerJackson2Module + * @since 5.1 + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class") +@JsonIgnoreProperties(ignoreUnknown = true) +class DefaultCsrfServerTokenMixin { + + /** + * JsonCreator constructor needed by Jackson to create {@link org.springframework.security.web.server.csrf.DefaultCsrfToken} + * object. + * + * @param headerName the name of the header + * @param parameterName the parameter name + * @param token the CSRF token value + */ + @JsonCreator + public DefaultCsrfServerTokenMixin(@JsonProperty("headerName") String headerName, + @JsonProperty("parameterName") String parameterName, @JsonProperty("token") String token) { + } +} diff --git a/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java b/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java new file mode 100644 index 00000000000..bf98ea2f11f --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java @@ -0,0 +1,51 @@ +/* + * Copyright 2015-2018 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.server.jackson2; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.module.SimpleModule; +import org.springframework.security.jackson2.SecurityJackson2Modules; +import org.springframework.security.web.server.csrf.DefaultCsrfToken; + +/** + * Jackson module for spring-security-web-flux. This module register {@link DefaultCsrfServerTokenMixin} + * If no default typing enabled by default then it'll enable it because typing info is needed to + * properly serialize/deserialize objects. + * In order to use this module just add this module into your ObjectMapper configuration. + * + *
+ *     ObjectMapper mapper = new ObjectMapper();
+ *     mapper.registerModule(new WebServerJackson2Module());
+ * 
+ * Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list of all security modules. + * + * @author Boris Finkelshteyn + * @see SecurityJackson2Modules + * @since 5.1 + */ +public class WebServerJackson2Module extends SimpleModule { + + public WebServerJackson2Module() { + super(WebServerJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null)); + } + + @Override + public void setupModule(SetupContext context) { + SecurityJackson2Modules.enableDefaultTyping(context.getOwner()); + context.setMixInAnnotations(DefaultCsrfToken.class, DefaultCsrfServerTokenMixin.class); + } +} diff --git a/web/src/test/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixinTests.java b/web/src/test/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixinTests.java new file mode 100644 index 00000000000..1126d158d44 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixinTests.java @@ -0,0 +1,73 @@ +/* + * 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.server.jackson2; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import org.json.JSONException; +import org.junit.Test; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.security.web.jackson2.AbstractMixinTests; +import org.springframework.security.web.server.csrf.DefaultCsrfToken; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Boris Finkelshteyn + * @since 5.1 + */ +public class DefaultCsrfServerTokenMixinTests extends AbstractMixinTests { + + // @formatter:off + private static final String CSRF_JSON = "{" + + "\"@class\": \"org.springframework.security.web.server.csrf.DefaultCsrfToken\", " + + "\"headerName\": \"csrf-header\", " + + "\"parameterName\": \"_csrf\", " + + "\"token\": \"1\"" + + "}"; + // @formatter:on + + @Test + public void defaultCsrfTokenSerializedTest() throws JsonProcessingException, JSONException { + DefaultCsrfToken token = new DefaultCsrfToken("csrf-header", "_csrf", "1"); + String serializedJson = mapper.writeValueAsString(token); + JSONAssert.assertEquals(CSRF_JSON, serializedJson, true); + } + + @Test + public void defaultCsrfTokenDeserializeTest() throws IOException { + DefaultCsrfToken token = mapper.readValue(CSRF_JSON, DefaultCsrfToken.class); + assertThat(token).isNotNull(); + assertThat(token.getHeaderName()).isEqualTo("csrf-header"); + assertThat(token.getParameterName()).isEqualTo("_csrf"); + assertThat(token.getToken()).isEqualTo("1"); + } + + @Test(expected = JsonMappingException.class) + public void defaultCsrfTokenDeserializeWithoutClassTest() throws IOException { + String tokenJson = "{\"headerName\": \"csrf-header\", \"parameterName\": \"_csrf\", \"token\": \"1\"}"; + mapper.readValue(tokenJson, DefaultCsrfToken.class); + } + + @Test(expected = JsonMappingException.class) + public void defaultCsrfTokenDeserializeNullValuesTest() throws IOException { + String tokenJson = "{\"@class\": \"org.springframework.security.web.server.csrf.DefaultCsrfToken\", \"headerName\": \"\", \"parameterName\": null, \"token\": \"1\"}"; + mapper.readValue(tokenJson, DefaultCsrfToken.class); + } +}