Polish Saml2 Jackson Support

Issue gh-10905
This commit is contained in:
Josh Cummings 2022-03-01 12:02:55 -07:00
parent df84826c95
commit 6c3d183a94
7 changed files with 32 additions and 195 deletions

View File

@ -47,7 +47,7 @@ class UnmodifiableMapDeserializerTests extends AbstractMixinTests {
Collections.unmodifiableMap(Collections.emptyMap()).getClass());
assertThat(map).isNotNull().isInstanceOf(Collections.unmodifiableMap(Collections.emptyMap()).getClass())
.containsAllEntriesOf(Map.of("Key", "Value"));
.containsAllEntriesOf(Collections.singletonMap("Key", "Value"));
}
}

View File

@ -1,66 +0,0 @@
/*
* 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.saml2.jackson2;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonParser;
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 org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
/**
* Custom deserializer for {@link DefaultSaml2AuthenticatedPrincipal}.
*
* @author Ulrich Grave
* @since 5.7
* @see DefaultSaml2AuthenticatedPrincipalMixin
*/
class DefaultSaml2AuthenticatedPrincipalDeserializer extends JsonDeserializer<DefaultSaml2AuthenticatedPrincipal> {
private static final TypeReference<List<String>> SESSION_INDICES_LIST = new TypeReference<List<String>>() {
};
private static final TypeReference<Map<String, List<Object>>> ATTRIBUTES_MAP = new TypeReference<Map<String, List<Object>>>() {
};
@Override
public DefaultSaml2AuthenticatedPrincipal deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
String name = JsonNodeUtils.findStringValue(jsonNode, "name");
Map<String, List<Object>> attributes = JsonNodeUtils.findValue(jsonNode, "attributes", ATTRIBUTES_MAP, mapper);
List<String> sessionIndexes = JsonNodeUtils.findValue(jsonNode, "sessionIndexes", SESSION_INDICES_LIST, mapper);
String registrationId = JsonNodeUtils.findStringValue(jsonNode, "registrationId");
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(name, attributes,
sessionIndexes);
if (registrationId != null) {
principal.setRelyingPartyRegistrationId(registrationId);
}
return principal;
}
}

View File

@ -16,9 +16,13 @@
package org.springframework.security.saml2.jackson2;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal;
@ -40,7 +44,16 @@ import org.springframework.security.saml2.provider.service.authentication.Defaul
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = DefaultSaml2AuthenticatedPrincipalDeserializer.class)
@JsonIgnoreProperties(ignoreUnknown = true)
class DefaultSaml2AuthenticatedPrincipalMixin {
@JsonProperty("registrationId")
String registrationId;
DefaultSaml2AuthenticatedPrincipalMixin(@JsonProperty("name") String name,
@JsonProperty("attributes") Map<String, List<Object>> attributes,
@JsonProperty("sessionIndexes") List<String> sessionIndexes) {
}
}

View File

@ -1,50 +0,0 @@
/*
* 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.saml2.jackson2;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
final class JsonNodeUtils {
private JsonNodeUtils() {
}
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
ObjectMapper mapper) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
}
static JsonNode readJsonNode(JsonNode jsonNode, String field) {
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
}
}

View File

@ -1,71 +0,0 @@
/*
* 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.saml2.jackson2;
import java.io.IOException;
import java.util.List;
import com.fasterxml.jackson.core.JsonParser;
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 org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
/**
* Custom deserializer for {@link Saml2Authentication}.
*
* @author Ulrich Grave
* @since 5.7
* @see Saml2AuthenticationMixin
*/
class Saml2AuthenticationDeserializer extends JsonDeserializer<Saml2Authentication> {
private static final TypeReference<List<GrantedAuthority>> GRANTED_AUTHORITY_LIST = new TypeReference<List<GrantedAuthority>>() {
};
private static final TypeReference<Object> OBJECT = new TypeReference<Object>() {
};
@Override
public Saml2Authentication deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
JsonNode jsonNode = mapper.readTree(jp);
boolean authenticated = JsonNodeUtils.readJsonNode(jsonNode, "authenticated").asBoolean();
JsonNode principalNode = JsonNodeUtils.readJsonNode(jsonNode, "principal");
AuthenticatedPrincipal principal = getPrincipal(mapper, principalNode);
String saml2Response = JsonNodeUtils.findStringValue(jsonNode, "saml2Response");
List<GrantedAuthority> authorities = JsonNodeUtils.findValue(jsonNode, "authorities", GRANTED_AUTHORITY_LIST,
mapper);
Object details = JsonNodeUtils.findValue(jsonNode, "details", OBJECT, mapper);
Saml2Authentication authentication = new Saml2Authentication(principal, saml2Response, authorities);
authentication.setAuthenticated(authenticated);
authentication.setDetails(details);
return authentication;
}
private AuthenticatedPrincipal getPrincipal(ObjectMapper mapper, JsonNode principalNode) throws IOException {
return mapper.readValue(principalNode.traverse(mapper), AuthenticatedPrincipal.class);
}
}

View File

@ -16,10 +16,16 @@
package org.springframework.security.saml2.jackson2;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.AuthenticatedPrincipal;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
@ -40,7 +46,13 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonDeserialize(using = Saml2AuthenticationDeserializer.class)
@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true)
class Saml2AuthenticationMixin {
@JsonCreator
Saml2AuthenticationMixin(@JsonProperty("principal") AuthenticatedPrincipal principal,
@JsonProperty("saml2Response") String saml2Response,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
}
}

View File

@ -204,7 +204,6 @@ final class TestSaml2JsonPayloads {
+ " \"credentialsNonExpired\": true,"
+ " \"enabled\": true"
+ " },"
+ " \"authenticated\": true,"
+ " \"principal\": " + DEFAULT_AUTHENTICATED_PRINCIPAL_JSON + ","
+ " \"saml2Response\": \"" + SAML_RESPONSE + "\""
+ "}";