Add Jackson Support for Saml2 Module

Closes gh-10905
This commit is contained in:
Ulrich Grave
2022-02-24 23:50:09 +01:00
committed by Josh Cummings
parent ff15bec02d
commit df84826c95
21 changed files with 1251 additions and 0 deletions

View File

@@ -64,6 +64,8 @@ public class CoreJackson2Module extends SimpleModule {
UnmodifiableSetMixin.class);
context.setMixInAnnotations(Collections.<Object>unmodifiableList(Collections.emptyList()).getClass(),
UnmodifiableListMixin.class);
context.setMixInAnnotations(Collections.<Object, Object>unmodifiableMap(Collections.emptyMap()).getClass(),
UnmodifiableMapMixin.class);
context.setMixInAnnotations(User.class, UserMixin.class);
context.setMixInAnnotations(UsernamePasswordAuthenticationToken.class,
UsernamePasswordAuthenticationTokenMixin.class);

View File

@@ -63,6 +63,7 @@ import org.springframework.util.ClassUtils;
* mapper.registerModule(new WebServletJackson2Module());
* mapper.registerModule(new WebServerJackson2Module());
* mapper.registerModule(new OAuth2ClientJackson2Module());
* mapper.registerModule(new Saml2Jackson2Module());
* </pre>
*
* @author Jitendra Singh.
@@ -86,6 +87,8 @@ public final class SecurityJackson2Modules {
private static final String ldapJackson2ModuleClass = "org.springframework.security.ldap.jackson2.LdapJackson2Module";
private static final String saml2Jackson2ModuleClass = "org.springframework.security.saml2.jackson2.Saml2Jackson2Module";
private SecurityJackson2Modules() {
}
@@ -134,6 +137,9 @@ public final class SecurityJackson2Modules {
if (ClassUtils.isPresent(ldapJackson2ModuleClass, loader)) {
addToModulesList(loader, modules, ldapJackson2ModuleClass);
}
if (ClassUtils.isPresent(saml2Jackson2ModuleClass, loader)) {
addToModulesList(loader, modules, saml2Jackson2ModuleClass);
}
return modules;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2022 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
*
* https://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.jackson2;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Custom deserializer for {@link UnmodifiableMapMixin}.
*
* @author Ulrich Grave
* @since 5.7
* @see UnmodifiableMapMixin
*/
class UnmodifiableMapDeserializer extends JsonDeserializer<Map<?, ?>> {
@Override
public Map<?, ?> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode node = mapper.readTree(jp);
Map<String, Object> result = new LinkedHashMap<>();
if (node != null && node.isObject()) {
Iterable<Map.Entry<String, JsonNode>> fields = node::fields;
for (Map.Entry<String, JsonNode> field : fields) {
result.put(field.getKey(), mapper.readValue(field.getValue().traverse(mapper), Object.class));
}
}
return Collections.unmodifiableMap(result);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2022 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
*
* https://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.jackson2;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
/**
* This mixin class used to deserialize java.util.Collections$UnmodifiableMap and used
* with various AuthenticationToken implementation's mixin classes.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CoreJackson2Module());
* </pre>
*
* @author Ulrich Grave
* @since 5.7
* @see UnmodifiableMapDeserializer
* @see CoreJackson2Module
* @see SecurityJackson2Modules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = UnmodifiableMapDeserializer.class)
class UnmodifiableMapMixin {
@JsonCreator
UnmodifiableMapMixin(Map<?, ?> map) {
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2002-2022 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
*
* https://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.jackson2;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import static org.assertj.core.api.Assertions.assertThat;
class UnmodifiableMapDeserializerTests extends AbstractMixinTests {
// @formatter:off
private static final String DEFAULT_MAP_JSON = "{"
+ "\"@class\": \"java.util.Collections$UnmodifiableMap\","
+ "\"Key\": \"Value\""
+ "}";
// @formatter:on
@Test
void shouldSerialize() throws Exception {
String mapJson = mapper
.writeValueAsString(Collections.unmodifiableMap(Collections.singletonMap("Key", "Value")));
JSONAssert.assertEquals(DEFAULT_MAP_JSON, mapJson, true);
}
@Test
void shouldDeserialize() throws Exception {
Map<String, String> map = mapper.readValue(DEFAULT_MAP_JSON,
Collections.unmodifiableMap(Collections.emptyMap()).getClass());
assertThat(map).isNotNull().isInstanceOf(Collections.unmodifiableMap(Collections.emptyMap()).getClass())
.containsAllEntriesOf(Map.of("Key", "Value"));
}
}