diff --git a/core/src/main/java/org/springframework/security/jackson/CoreJacksonModule.java b/core/src/main/java/org/springframework/security/jackson/CoreJacksonModule.java index 0d633f4c2de..60247c7a076 100644 --- a/core/src/main/java/org/springframework/security/jackson/CoreJacksonModule.java +++ b/core/src/main/java/org/springframework/security/jackson/CoreJacksonModule.java @@ -94,7 +94,13 @@ public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Buil .allowIfSubType("java.util.Collections$SingletonList") .allowIfSubType("java.util.TreeMap") .allowIfSubType("java.util.HashSet") - .allowIfSubType("java.util.LinkedHashSet"); + .allowIfSubType("java.util.LinkedHashSet") + .allowIfSubType("java.util.ImmutableCollections$List12") + .allowIfSubType("java.util.ImmutableCollections$ListN") + .allowIfSubType("java.util.ImmutableCollections$Set12") + .allowIfSubType("java.util.ImmutableCollections$SetN") + .allowIfSubType("java.util.ImmutableCollections$Map1") + .allowIfSubType("java.util.ImmutableCollections$MapN"); } @Override diff --git a/core/src/test/java/org/springframework/security/jackson/ImmutableCollectionsTests.java b/core/src/test/java/org/springframework/security/jackson/ImmutableCollectionsTests.java new file mode 100644 index 00000000000..fc4e3b874d5 --- /dev/null +++ b/core/src/test/java/org/springframework/security/jackson/ImmutableCollectionsTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2004-present 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.jackson; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import tools.jackson.core.type.TypeReference; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that the JDK immutable collections created by {@code List.of}, {@code Set.of}, + * {@code Map.of} and {@code Stream.toList()} can be serialized and deserialized. + * + * @author Rajesh Vasa + */ +class ImmutableCollectionsTests extends AbstractMixinTests { + + private static final TypeReference> MAP_TYPE = new TypeReference<>() { + }; + + @Test + void listOfOneElementWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(List.of("a")); + } + + @Test + void listOfManyElementsWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(List.of("a", "b", "c")); + } + + @Test + void emptyListOfWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(List.of()); + } + + @Test + void streamToListWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(Stream.of("ROLE_USER", "ROLE_ADMIN").toList()); + } + + @Test + void setOfOneElementWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(Set.of("a")); + } + + @Test + void setOfManyElementsWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(Set.of("a", "b", "c")); + } + + @Test + void mapOfOneEntryWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(Map.of("k", "v")); + } + + @Test + void mapOfManyEntriesWhenRoundTripThenDeserialized() throws Exception { + assertRoundTrip(Map.of("k1", "v1", "k2", "v2")); + } + + private void assertRoundTrip(Object value) throws Exception { + Map claims = new HashMap<>(); + claims.put("value", value); + String json = this.mapper.writeValueAsString(claims); + Map deserialized = this.mapper.readValue(json, MAP_TYPE); + assertThat(deserialized.get("value")).isEqualTo(value); + } + +} diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java index 04f7a9a3103..048593a7aca 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -355,6 +356,30 @@ public void findByTokenWhenAccessTokenExistsThenFound() { assertThat(authorization).isEqualTo(result); } + // gh-20084 + @Test + public void findByTokenWhenAccessTokenClaimsContainImmutableCollectionsThenFound() { + given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT); + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", + Instant.now().minusSeconds(60).truncatedTo(ChronoUnit.MILLIS), + Instant.now().truncatedTo(ChronoUnit.MILLIS)); + Map claims = Map.of("roles", Stream.of("ROLE_USER", "ROLE_ADMIN").toList(), "scope", + Set.of("read", "write"), "aud", List.of("client"), "attributes", Map.of("k", "v")); + OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(AUTHORIZATION_CODE) + .token(accessToken, (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claims)) + .build(); + this.authorizationService.save(authorization); + + OAuth2Authorization result = this.authorizationService.findByToken(accessToken.getTokenValue(), + OAuth2TokenType.ACCESS_TOKEN); + assertThat(result).isNotNull(); + assertThat(result.getAccessToken().getClaims()).isEqualTo(claims); + } + @Test public void findByTokenWhenIdTokenExistsThenFound() { given(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))).willReturn(REGISTERED_CLIENT);