From b838f7c7b7e079c539d1247f8d87013041eb20d1 Mon Sep 17 00:00:00 2001 From: finke-ba Date: Tue, 18 Dec 2018 13:47:35 +0200 Subject: [PATCH] Add WebFlux support for spring security web jackson module. Fixes: gh-6303 --- .../jackson2/SecurityJackson2Modules.java | 4 +- .../jackson2/DefaultCsrfServerTokenMixin.java | 53 ++++++++++++++ .../jackson2/WebServerJackson2Module.java | 51 +++++++++++++ .../DefaultCsrfServerTokenMixinTests.java | 73 +++++++++++++++++++ 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 web/src/main/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixin.java create mode 100644 web/src/main/java/org/springframework/security/web/server/jackson2/WebServerJackson2Module.java create mode 100644 web/src/test/java/org/springframework/security/web/server/jackson2/DefaultCsrfServerTokenMixinTests.java 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 ee5ba929f7..dba5bcc6d4 100644 --- a/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java +++ b/core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java @@ -58,6 +58,7 @@ import java.util.Set; * mapper.registerModule(new CasJackson2Module()); * mapper.registerModule(new WebJackson2Module()); * mapper.registerModule(new WebServletJackson2Module()); + * mapper.registerModule(new WebServerJackson2Module()); * * * @author Jitendra Singh. @@ -69,7 +70,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 static final String webServletJackson2ModuleClass = "org.springframework.security.web.jackson2.WebServletJackson2Module"; 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 0000000000..496900a503 --- /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 0000000000..bf98ea2f11 --- /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 0000000000..1126d158d4 --- /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); + } +}