1
0
mirror of synced 2026-05-22 21:33:16 +00:00

Add Jackson 3 support

This commit adds support for Jackson 3 which has the following
major differences with the Jackson 2 one:
 - jackson subpackage instead of jackson2
 - Jackson type prefix instead of Jackson2
 - JsonMapper instead of ObjectMapper
 - For configuration, JsonMapper.Builder instead of ObjectMapper
   since the latter is now immutable
 - Remove custom support for unmodifiable collections
 - Use safe default typing via a PolymorphicTypeValidator

Jackson 3 changes compared to Jackson 2 are documented in
https://cowtowncoder.medium.com/jackson-3-0-0-ga-released-1f669cda529a
and
https://github.com/FasterXML/jackson/blob/main/jackson3/MIGRATING_TO_JACKSON_3.md.

This commit does not cover webauthn which is a special case (uses
jackson sub-package for Jackson 2 support) which will be handled in
a distinct commit.

See gh-17832
Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
This commit is contained in:
Sébastien Deleuze
2025-09-01 18:23:31 +02:00
committed by Rob Winch
parent 916a687b29
commit 65a14d6c6d
156 changed files with 9052 additions and 146 deletions
@@ -11,10 +11,11 @@ dependencies {
exclude group: "commons-logging", module: "commons-logging"
}
api "com.nimbusds:nimbus-jose-jwt"
api "com.fasterxml.jackson.core:jackson-databind"
api 'tools.jackson.core:jackson-databind'
optional "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
optional "org.springframework:spring-jdbc"
optional "com.fasterxml.jackson.core:jackson-databind"
testImplementation project(":spring-security-test")
testImplementation project(path : ':spring-security-oauth2-jose', configuration : 'tests')
@@ -33,13 +33,15 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
@@ -64,8 +66,10 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.jackson.OAuth2AuthorizationServerJacksonModule;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -469,16 +473,12 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private LobHandler lobHandler = new DefaultLobHandler();
private ObjectMapper objectMapper = new ObjectMapper();
private Mapper mapper = (ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper",
OAuth2AuthorizationRowMapper.class.getClassLoader())) ? new JacksonDelegate() : new Jackson2Delegate();
public OAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository) {
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
this.registeredClientRepository = registeredClientRepository;
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@@ -623,9 +623,9 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
this.lobHandler = lobHandler;
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
public final void setMapper(Mapper mapper) {
Assert.notNull(mapper, "objectMapper cannot be null");
this.mapper = mapper;
}
protected final RegisteredClientRepository getRegisteredClientRepository() {
@@ -636,13 +636,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return this.lobHandler;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
protected final Mapper getMapper() {
return this.mapper;
}
private Map<String, Object> parseMap(String data) {
try {
return this.objectMapper.readValue(data, new TypeReference<>() {
return this.mapper.readValue(data, new ParameterizedTypeReference<>() {
});
}
catch (Exception ex) {
@@ -659,13 +659,10 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
public static class OAuth2AuthorizationParametersMapper
implements Function<OAuth2Authorization, List<SqlParameterValue>> {
private ObjectMapper objectMapper = new ObjectMapper();
private Mapper mapper = (ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper",
OAuth2AuthorizationRowMapper.class.getClassLoader())) ? new JacksonDelegate() : new Jackson2Delegate();
public OAuth2AuthorizationParametersMapper() {
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
@@ -737,13 +734,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return parameters;
}
public final void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "objectMapper cannot be null");
this.objectMapper = objectMapper;
public final void setMapper(Mapper mapper) {
Assert.notNull(mapper, "mapper cannot be null");
this.mapper = mapper;
}
protected final ObjectMapper getObjectMapper() {
return this.objectMapper;
protected final Mapper getMapper() {
return this.mapper;
}
private <T extends OAuth2Token> List<SqlParameterValue> toSqlParameterList(String tokenColumnName,
@@ -774,7 +771,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private String writeMap(Map<String, Object> data) {
try {
return this.objectMapper.writeValueAsString(data);
return this.mapper.writeValueAsString(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
@@ -851,4 +848,74 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
public interface Mapper {
String writeValueAsString(Object data);
<T> T readValue(String value, ParameterizedTypeReference<T> typeReference);
}
@SuppressWarnings("removal")
public static class Jackson2Delegate implements Mapper {
private final ObjectMapper objectMapper = new ObjectMapper();
public Jackson2Delegate() {
ClassLoader classLoader = Jackson2Delegate.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
this.objectMapper.registerModules(securityModules);
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
}
@Override
public String writeValueAsString(Object data) {
try {
return this.objectMapper.writeValueAsString(data);
}
catch (JsonProcessingException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
@Override
public <T> T readValue(String value, ParameterizedTypeReference<T> typeReference) {
try {
com.fasterxml.jackson.databind.JavaType javaType = this.objectMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.objectMapper.readValue(value, javaType);
}
catch (JsonProcessingException ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
}
}
}
public static class JacksonDelegate implements Mapper {
private final JsonMapper jsonMapper;
public JacksonDelegate() {
this.jsonMapper = JsonMapper.builder().addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
public JacksonDelegate(JsonMapper.Builder builder) {
this.jsonMapper = builder.addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
@Override
public String writeValueAsString(Object data) {
return this.jsonMapper.writeValueAsString(data);
}
@Override
public <T> T readValue(String value, ParameterizedTypeReference<T> typeReference) {
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
.constructType(typeReference.getType());
return this.jsonMapper.readValue(value, javaType);
}
}
}
@@ -0,0 +1,66 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.Map;
import java.util.Set;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
/**
* Utility class for {@code JsonNode}.
*
* @author Joe Grandja
* @since 7.0
*/
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isString()) ? value.stringValue() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
DeserializationContext context) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainer())
? context.readTreeAsValue(value, context.getTypeFactory().constructType(valueTypeReference)) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}
@@ -0,0 +1,36 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* This mixin class is used to serialize/deserialize {@link SignatureAlgorithm}.
*
* @author Joe Grandja
* @since 7.0
* @see SignatureAlgorithm
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class JwsAlgorithmMixin {
}
@@ -0,0 +1,77 @@
/*
* 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.oauth2.server.authorization.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.exc.InvalidFormatException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestMixin
*/
final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAuth2AuthorizationRequest> {
@Override
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) {
JsonNode root = context.readTree(parser);
return deserialize(parser, context, root);
}
private OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context, JsonNode root) {
AuthorizationGrantType authorizationGrantType = convertAuthorizationGrantType(
JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, context));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, context));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, context));
return builder.build();
}
private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.authorizationCode();
}
throw new InvalidFormatException(parser, "Invalid authorizationGrantType", authorizationGrantType,
AuthorizationGrantType.class);
}
private static AuthorizationGrantType convertAuthorizationGrantType(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
return null;
}
}
@@ -0,0 +1,40 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
* It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestDeserializer
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizationRequestMixin {
}
@@ -0,0 +1,95 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.net.URL;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.core.Version;
import tools.jackson.databind.DefaultTyping;
import tools.jackson.databind.cfg.MapperBuilder;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.jackson.CoreJacksonModule;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* Jackson {@code Module} for {@code spring-security-oauth2-authorization-server}, that
* registers the following mix-in annotations:
*
* <ul>
* <li>{@link OAuth2TokenExchangeActor}</li>
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
* <li>{@link OAuth2TokenExchangeCompositeAuthenticationTokenMixin}</li>
* <li>{@link JwsAlgorithmMixin}</li>
* <li>{@link OAuth2TokenFormatMixin}</li>
* </ul>
*
* If not already enabled, default typing will be automatically enabled as type info is
* required to properly serialize/deserialize objects. In order to use this module just
* add it to your {@code JsonMapper.Builder} configuration.
*
* <pre>
* JsonMapper mapper = JsonMapper.builder()
* .addModules(new OAuth2AuthorizationServerJacksonModule()).build;
* </pre>
*
* @author Sebastien Deleuze
* @author Steve Riesenberg
* @since 7.0
*/
@SuppressWarnings("serial")
public class OAuth2AuthorizationServerJacksonModule extends CoreJacksonModule {
public OAuth2AuthorizationServerJacksonModule() {
super(OAuth2AuthorizationServerJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
super.configurePolymorphicTypeValidator(builder);
builder.allowIfSubType(OAuth2TokenFormat.class)
.allowIfSubType(OAuth2TokenExchangeActor.class)
.allowIfSubType(OAuth2TokenExchangeCompositeAuthenticationToken.class)
.allowIfSubType(SignatureAlgorithm.class)
.allowIfSubType(MacAlgorithm.class)
.allowIfSubType(OAuth2AuthorizationRequest.class)
.allowIfSubType(URL.class);
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
BasicPolymorphicTypeValidator.Builder builder = BasicPolymorphicTypeValidator.builder();
this.configurePolymorphicTypeValidator(builder);
((MapperBuilder<?, ?>) context.getOwner()).activateDefaultTyping(builder.build(), DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
context.setMixIn(OAuth2TokenExchangeActor.class, OAuth2TokenExchangeActorMixin.class);
context.setMixIn(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
context.setMixIn(OAuth2TokenExchangeCompositeAuthenticationToken.class,
OAuth2TokenExchangeCompositeAuthenticationTokenMixin.class);
context.setMixIn(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixIn(MacAlgorithm.class, JwsAlgorithmMixin.class);
context.setMixIn(OAuth2TokenFormat.class, OAuth2TokenFormatMixin.class);
}
}
@@ -0,0 +1,44 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeActor}.
*
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2TokenExchangeActor
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenExchangeActorMixin {
@JsonCreator
OAuth2TokenExchangeActorMixin(@JsonProperty("claims") Map<String, Object> claims) {
}
}
@@ -0,0 +1,47 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
/**
* This mixin class is used to serialize/deserialize
* {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
*
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2TokenExchangeCompositeAuthenticationToken
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenExchangeCompositeAuthenticationTokenMixin {
@JsonCreator
OAuth2TokenExchangeCompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
@JsonProperty("actors") List<Authentication> actors) {
}
}
@@ -0,0 +1,42 @@
/*
* 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.oauth2.server.authorization.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2TokenFormat}.
*
* @author Joe Grandja
* @since 7.0
* @see OAuth2TokenFormat
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2TokenFormatMixin {
@JsonCreator
OAuth2TokenFormatMixin(@JsonProperty("value") String value) {
}
}
@@ -29,11 +29,11 @@ import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
@@ -747,7 +747,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
private Map<String, Object> parseMap(String data) {
try {
return getObjectMapper().readValue(data, new TypeReference<>() {
return getMapper().readValue(data, new ParameterizedTypeReference<>() {
});
}
catch (Exception ex) {
@@ -852,7 +852,7 @@ public class JdbcOAuth2AuthorizationServiceTests {
private String writeMap(Map<String, Object> data) {
try {
return getObjectMapper().writeValueAsString(data);
return getMapper().writeValueAsString(data);
}
catch (Exception ex) {
throw new IllegalArgumentException(ex.getMessage(), ex);
@@ -365,6 +365,7 @@ public class JdbcRegisteredClientRepositoryTests {
return !result.isEmpty() ? result.get(0) : null;
}
@SuppressWarnings("removal")
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
private final ObjectMapper objectMapper = new ObjectMapper();
@@ -0,0 +1,112 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link OAuth2AuthorizationServerJackson2Module}.
*
* @author Steve Riesenberg
* @author Joe Grandja
*/
@SuppressWarnings("removal")
public class OAuth2AuthorizationServerJacksonModuleTests {
private static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
private JsonMapper mapper;
@BeforeEach
public void setup() {
this.mapper = JsonMapper.builder().addModules(new OAuth2AuthorizationServerJacksonModule()).build();
}
@Test
public void readValueWhenOAuth2AuthorizationAttributesThenSuccess() {
Authentication principal = new UsernamePasswordAuthenticationToken("principal", "credentials");
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
.attributes((attrs) -> attrs.put(Principal.class.getName(), principal))
.build();
Map<String, Object> attributes = authorization.getAttributes();
String json = this.mapper.writeValueAsString(attributes);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(attributes);
}
@Test
public void readValueWhenOAuth2AccessTokenMetadataThenSuccess() {
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
Map<String, Object> metadata = authorization.getAccessToken().getMetadata();
String json = this.mapper.writeValueAsString(metadata);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(metadata);
}
@Test
public void readValueWhenClientSettingsThenSuccess() {
ClientSettings clientSettings = ClientSettings.builder()
.tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
.build();
Map<String, Object> clientSettingsMap = clientSettings.getSettings();
String json = this.mapper.writeValueAsString(clientSettingsMap);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(clientSettingsMap);
}
@Test
public void readValueWhenTokenSettingsThenSuccess() {
TokenSettings tokenSettings = TokenSettings.builder().build();
Map<String, Object> tokenSettingsMap = tokenSettings.getSettings();
String json = this.mapper.writeValueAsString(tokenSettingsMap);
assertThat(this.mapper.readValue(json, STRING_OBJECT_MAP)).isEqualTo(tokenSettingsMap);
}
@Test
public void readValueWhenOAuth2TokenExchangeCompositeAuthenticationTokenThenSuccess() {
Authentication subject = new UsernamePasswordAuthenticationToken("principal", "credentials");
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(
Map.of(JwtClaimNames.ISS, "issuer-1", JwtClaimNames.SUB, "actor1"));
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(
Map.of(JwtClaimNames.ISS, "issuer-2", JwtClaimNames.SUB, "actor2"));
OAuth2TokenExchangeCompositeAuthenticationToken authentication = new OAuth2TokenExchangeCompositeAuthenticationToken(
subject, List.of(actor1, actor2));
String json = this.mapper.writeValueAsString(authentication);
assertThat(this.mapper.readValue(json, OAuth2TokenExchangeCompositeAuthenticationToken.class))
.isEqualTo(authentication);
}
}
@@ -0,0 +1,49 @@
/*
* 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.oauth2.server.authorization.jackson;
import java.util.List;
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.authentication.TestingAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
/**
* This mixin class is used to serialize/deserialize {@link TestingAuthenticationToken}.
*
* @author Steve Riesenberg
* @since 7.0
* @see TestingAuthenticationToken
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(value = { "authenticated" }, ignoreUnknown = true)
public class TestingAuthenticationTokenMixin {
@JsonCreator
TestingAuthenticationTokenMixin(@JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") List<GrantedAuthority> authorities) {
}
}
@@ -15,6 +15,7 @@ dependencies {
optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-r2dbc'
optional 'tools.jackson.core:jackson-databind'
testImplementation project(path: ':spring-security-oauth2-core', configuration: 'tests')
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')
@@ -0,0 +1,76 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
/**
* A {@code JsonDeserializer} for {@link ClientRegistration}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see ClientRegistration
* @see ClientRegistrationMixin
*/
final class ClientRegistrationDeserializer extends ValueDeserializer<ClientRegistration> {
private static final StdConverter<JsonNode, ClientAuthenticationMethod> CLIENT_AUTHENTICATION_METHOD_CONVERTER = new StdConverters.ClientAuthenticationMethodConverter();
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
private static final StdConverter<JsonNode, AuthenticationMethod> AUTHENTICATION_METHOD_CONVERTER = new StdConverters.AuthenticationMethodConverter();
@Override
public ClientRegistration deserialize(JsonParser parser, DeserializationContext context) {
JsonNode clientRegistrationNode = context.readTree(parser);
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
return ClientRegistration
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
.clientSecret(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientSecret"))
.clientAuthenticationMethod(CLIENT_AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "clientAuthenticationMethod")))
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, context))
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
.userInfoUri(JsonNodeUtils.findStringValue(userInfoEndpointNode, "uri"))
.userInfoAuthenticationMethod(AUTHENTICATION_METHOD_CONVERTER
.convert(JsonNodeUtils.findObjectNode(userInfoEndpointNode, "authenticationMethod")))
.userNameAttributeName(JsonNodeUtils.findStringValue(userInfoEndpointNode, "userNameAttributeName"))
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
JsonNodeUtils.STRING_OBJECT_MAP, context))
.build();
}
}
@@ -0,0 +1,42 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
/**
* This mixin class is used to serialize/deserialize {@link ClientRegistration}. It also
* registers a custom deserializer {@link ClientRegistrationDeserializer}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see ClientRegistration
* @see ClientRegistrationDeserializer
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = ClientRegistrationDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class ClientRegistrationMixin {
}
@@ -0,0 +1,50 @@
/*
* 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.oauth2.client.jackson;
import java.util.Collection;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
/**
* This mixin class is used to serialize/deserialize {@link DefaultOAuth2User}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see DefaultOAuth2User
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class DefaultOAuth2UserMixin {
@JsonCreator
DefaultOAuth2UserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("attributes") Map<String, Object> attributes,
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
}
}
@@ -0,0 +1,53 @@
/*
* 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.oauth2.client.jackson;
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.GrantedAuthority;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
/**
* This mixin class is used to serialize/deserialize {@link DefaultOidcUser}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see DefaultOidcUser
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "attributes" })
abstract class DefaultOidcUserMixin {
@JsonCreator
DefaultOidcUserMixin(@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("idToken") OidcIdToken idToken, @JsonProperty("userInfo") OidcUserInfo userInfo,
@JsonProperty("nameAttributeKey") String nameAttributeKey) {
}
}
@@ -0,0 +1,67 @@
/*
* 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.oauth2.client.jackson;
import java.util.Map;
import java.util.Set;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
/**
* Utility class for {@code JsonNode}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isString()) ? value.stringValue() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
DeserializationContext context) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainer())
? context.readTreeAsValue(value, context.getTypeFactory().constructType(valueTypeReference)) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}
@@ -0,0 +1,52 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AccessToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AccessToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AccessTokenMixin {
@JsonCreator
OAuth2AccessTokenMixin(
@JsonProperty("tokenType") @JsonDeserialize(
converter = StdConverters.AccessTokenTypeConverter.class) OAuth2AccessToken.TokenType tokenType,
@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("scopes") Set<String> scopes) {
}
}
@@ -0,0 +1,56 @@
/*
* 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.oauth2.client.jackson;
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.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
/**
* This mixin class is used to serialize/deserialize
* {@link OAuth2AuthenticationException}.
*
* @author Sebastien Deleuze
* @author Dennis Neufeld
* @author Steve Riesenberg
* @since 7.0
* @see OAuth2AuthenticationException
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "cause", "stackTrace", "suppressedExceptions" })
abstract class OAuth2AuthenticationExceptionMixin {
@JsonProperty("error")
abstract OAuth2Error getError();
@JsonProperty("detailMessage")
abstract String getMessage();
@JsonCreator
OAuth2AuthenticationExceptionMixin(@JsonProperty("error") OAuth2Error error,
@JsonProperty("detailMessage") String message) {
}
}
@@ -0,0 +1,52 @@
/*
* 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.oauth2.client.jackson;
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.GrantedAuthority;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthenticationToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0O
* @see OAuth2AuthenticationToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "authenticated" })
abstract class OAuth2AuthenticationTokenMixin {
@JsonCreator
OAuth2AuthenticationTokenMixin(@JsonProperty("principal") OAuth2User principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("authorizedClientRegistrationId") String authorizedClientRegistrationId) {
}
}
@@ -0,0 +1,72 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.JsonParser;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ValueDeserializer;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestMixin
*/
final class OAuth2AuthorizationRequestDeserializer extends ValueDeserializer<OAuth2AuthorizationRequest> {
private static final StdConverter<JsonNode, AuthorizationGrantType> AUTHORIZATION_GRANT_TYPE_CONVERTER = new StdConverters.AuthorizationGrantTypeConverter();
@Override
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context) {
JsonNode root = context.readTree(parser);
return deserialize(parser, context, root);
}
private OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context, JsonNode root) {
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, context));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, context));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, context));
return builder.build();
}
private Builder getBuilder(JsonParser parser, AuthorizationGrantType authorizationGrantType) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.authorizationCode();
}
throw new StreamReadException(parser, "Invalid authorizationGrantType");
}
}
@@ -0,0 +1,42 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.annotation.JsonDeserialize;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizationRequest}.
* It also registers a custom deserializer {@link OAuth2AuthorizationRequestDeserializer}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizationRequest
* @see OAuth2AuthorizationRequestDeserializer
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonDeserialize(using = OAuth2AuthorizationRequestDeserializer.class)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizationRequestMixin {
}
@@ -0,0 +1,50 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2AuthorizedClient}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2AuthorizedClient
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2AuthorizedClientMixin {
@JsonCreator
OAuth2AuthorizedClientMixin(@JsonProperty("clientRegistration") ClientRegistration clientRegistration,
@JsonProperty("principalName") String principalName,
@JsonProperty("accessToken") OAuth2AccessToken accessToken,
@JsonProperty("refreshToken") OAuth2RefreshToken refreshToken) {
}
}
@@ -0,0 +1,118 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.core.Version;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import org.springframework.security.jackson.SecurityJacksonModule;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
/**
* Jackson {@code Module} for {@code spring-security-oauth2-client}, that registers the
* following mix-in annotations:
*
* <ul>
* <li>{@link OAuth2AuthorizationRequestMixin}</li>
* <li>{@link ClientRegistrationMixin}</li>
* <li>{@link OAuth2AccessTokenMixin}</li>
* <li>{@link OAuth2RefreshTokenMixin}</li>
* <li>{@link OAuth2AuthorizedClientMixin}</li>
* <li>{@link OAuth2UserAuthorityMixin}</li>
* <li>{@link DefaultOAuth2UserMixin}</li>
* <li>{@link OidcIdTokenMixin}</li>
* <li>{@link OidcUserInfoMixin}</li>
* <li>{@link OidcUserAuthorityMixin}</li>
* <li>{@link DefaultOidcUserMixin}</li>
* <li>{@link OAuth2AuthenticationTokenMixin}</li>
* <li>{@link OAuth2AuthenticationExceptionMixin}</li>
* <li>{@link OAuth2ErrorMixin}</li>
* </ul>
*
* <p>
* The recommended way to configure it is to use {@link SecurityJacksonModules} in order
* to enable properly automatic inclusion of type information with related validation.
*
* <pre>
* ClassLoader loader = getClass().getClassLoader();
* JsonMapper mapper = JsonMapper.builder()
* .addModules(SecurityJacksonModules.getModules(loader))
* .build();
* </pre>
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
@SuppressWarnings("serial")
public class OAuth2ClientJacksonModule extends SecurityJacksonModule {
public OAuth2ClientJacksonModule() {
super(OAuth2ClientJacksonModule.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void configurePolymorphicTypeValidator(BasicPolymorphicTypeValidator.Builder builder) {
builder.allowIfSubType(OAuth2AuthenticationException.class)
.allowIfSubType(DefaultOidcUser.class)
.allowIfSubType(OAuth2AuthorizationRequest.class)
.allowIfSubType(OAuth2Error.class)
.allowIfSubType(OAuth2AuthorizedClient.class)
.allowIfSubType(OidcIdToken.class)
.allowIfSubType(OidcUserInfo.class)
.allowIfSubType(DefaultOAuth2User.class)
.allowIfSubType(ClientRegistration.class)
.allowIfSubType(OAuth2AccessToken.class)
.allowIfSubType(OAuth2RefreshToken.class)
.allowIfSubType(OAuth2AuthenticationToken.class)
.allowIfSubType(OidcUserAuthority.class)
.allowIfSubType(OAuth2UserAuthority.class);
}
@Override
public void setupModule(SetupContext context) {
context.setMixIn(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
context.setMixIn(ClientRegistration.class, ClientRegistrationMixin.class);
context.setMixIn(OAuth2AccessToken.class, OAuth2AccessTokenMixin.class);
context.setMixIn(OAuth2RefreshToken.class, OAuth2RefreshTokenMixin.class);
context.setMixIn(OAuth2AuthorizedClient.class, OAuth2AuthorizedClientMixin.class);
context.setMixIn(OAuth2UserAuthority.class, OAuth2UserAuthorityMixin.class);
context.setMixIn(DefaultOAuth2User.class, DefaultOAuth2UserMixin.class);
context.setMixIn(OidcIdToken.class, OidcIdTokenMixin.class);
context.setMixIn(OidcUserInfo.class, OidcUserInfoMixin.class);
context.setMixIn(OidcUserAuthority.class, OidcUserAuthorityMixin.class);
context.setMixIn(DefaultOidcUser.class, DefaultOidcUserMixin.class);
context.setMixIn(OAuth2AuthenticationToken.class, OAuth2AuthenticationTokenMixin.class);
context.setMixIn(OAuth2AuthenticationException.class, OAuth2AuthenticationExceptionMixin.class);
context.setMixIn(OAuth2Error.class, OAuth2ErrorMixin.class);
}
}
@@ -0,0 +1,47 @@
/*
* 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.oauth2.client.jackson;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.OAuth2Error;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2Error} as part of
* {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException}.
*
* @author Sebastien Deleuze
* @author Dennis Neufeld
* @since 7.0
* @see OAuth2Error
* @see OAuth2AuthenticationExceptionMixin
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2ErrorMixin {
@JsonCreator
OAuth2ErrorMixin(@JsonProperty("errorCode") String errorCode, @JsonProperty("description") String description,
@JsonProperty("uri") String uri) {
}
}
@@ -0,0 +1,46 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2RefreshToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2RefreshToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2RefreshTokenMixin {
@JsonCreator
OAuth2RefreshTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt) {
}
}
@@ -0,0 +1,47 @@
/*
* 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.oauth2.client.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
/**
* This mixin class is used to serialize/deserialize {@link OAuth2UserAuthority}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OAuth2UserAuthority
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OAuth2UserAuthorityMixin {
@JsonCreator
OAuth2UserAuthorityMixin(@JsonProperty("authority") String authority,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}
@@ -0,0 +1,48 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
/**
* This mixin class is used to serialize/deserialize {@link OidcIdToken}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcIdToken
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OidcIdTokenMixin {
@JsonCreator
OidcIdTokenMixin(@JsonProperty("tokenValue") String tokenValue, @JsonProperty("issuedAt") Instant issuedAt,
@JsonProperty("expiresAt") Instant expiresAt, @JsonProperty("claims") Map<String, Object> claims) {
}
}
@@ -0,0 +1,49 @@
/*
* 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.oauth2.client.jackson;
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.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
/**
* This mixin class is used to serialize/deserialize {@link OidcUserAuthority}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcUserAuthority
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties({ "attributes" })
abstract class OidcUserAuthorityMixin {
@JsonCreator
OidcUserAuthorityMixin(@JsonProperty("authority") String authority, @JsonProperty("idToken") OidcIdToken idToken,
@JsonProperty("userInfo") OidcUserInfo userInfo) {
}
}
@@ -0,0 +1,46 @@
/*
* 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.oauth2.client.jackson;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
/**
* This mixin class is used to serialize/deserialize {@link OidcUserInfo}.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
* @see OidcUserInfo
* @see OAuth2ClientJacksonModule
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
abstract class OidcUserInfoMixin {
@JsonCreator
OidcUserInfoMixin(@JsonProperty("claims") Map<String, Object> claims) {
}
}
@@ -0,0 +1,94 @@
/*
* 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.oauth2.client.jackson;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthenticationMethod;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
/**
* {@code StdConverter} implementations.
*
* @author Sebastien Deleuze
* @author Joe Grandja
* @since 7.0
*/
abstract class StdConverters {
static final class AccessTokenTypeConverter extends StdConverter<JsonNode, OAuth2AccessToken.TokenType> {
@Override
public OAuth2AccessToken.TokenType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(value)) {
return OAuth2AccessToken.TokenType.BEARER;
}
return null;
}
}
static final class ClientAuthenticationMethodConverter extends StdConverter<JsonNode, ClientAuthenticationMethod> {
@Override
public ClientAuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
return ClientAuthenticationMethod.valueOf(value);
}
}
static final class AuthorizationGrantTypeConverter extends StdConverter<JsonNode, AuthorizationGrantType> {
@Override
public AuthorizationGrantType convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
}
return new AuthorizationGrantType(value);
}
}
static final class AuthenticationMethodConverter extends StdConverter<JsonNode, AuthenticationMethod> {
@Override
public AuthenticationMethod convert(JsonNode jsonNode) {
String value = JsonNodeUtils.findStringValue(jsonNode, "value");
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.HEADER;
}
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.FORM;
}
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.QUERY;
}
return null;
}
}
}
@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Jackson 3+ serialization support for OAuth2 client.
*/
package org.springframework.security.oauth2.client.jackson;
@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Jackson 2 serialization support for OAuth2 client.
*/
package org.springframework.security.oauth2.client.jackson2;
@@ -0,0 +1,129 @@
/*
* 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.oauth2.client.jackson;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.databind.exc.ValueInstantiationException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationExceptionMixin}.
*
* @author Dennis Neufeld
* @since 5.3.4
*/
public class OAuth2AuthenticationExceptionMixinTests {
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]", "Authorization Request Not Found", "/foo/bar"),
"Authorization Request Not Found");
String serializedJson = this.mapper.writeValueAsString(exception);
String expected = asJson(exception);
JSONAssert.assertEquals(expected, serializedJson, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]"));
String serializedJson = this.mapper.writeValueAsString(exception);
String expected = asJson(exception);
JSONAssert.assertEquals(expected, serializedJson, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
String json = asJson(new OAuth2AuthenticationException(new OAuth2Error("[authorization_request_not_found]")));
assertThatExceptionOfType(ValueInstantiationException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthenticationException.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
OAuth2AuthenticationException expected = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]", "Authorization Request Not Found", "/foo/bar"),
"Authorization Request Not Found");
OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected),
OAuth2AuthenticationException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isEqualTo(expected.getMessage());
OAuth2Error oauth2Error = exception.getError();
assertThat(oauth2Error).isNotNull();
assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode());
assertThat(oauth2Error.getDescription()).isEqualTo(expected.getError().getDescription());
assertThat(oauth2Error.getUri()).isEqualTo(expected.getError().getUri());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
OAuth2AuthenticationException expected = new OAuth2AuthenticationException(
new OAuth2Error("[authorization_request_not_found]"));
OAuth2AuthenticationException exception = this.mapper.readValue(asJson(expected),
OAuth2AuthenticationException.class);
assertThat(exception).isNotNull();
assertThat(exception.getCause()).isNull();
assertThat(exception.getMessage()).isNull();
OAuth2Error oauth2Error = exception.getError();
assertThat(oauth2Error).isNotNull();
assertThat(oauth2Error.getErrorCode()).isEqualTo(expected.getError().getErrorCode());
assertThat(oauth2Error.getDescription()).isNull();
assertThat(oauth2Error.getUri()).isNull();
}
private String asJson(OAuth2AuthenticationException exception) {
OAuth2Error error = exception.getError();
// @formatter:off
return "\n{"
+ "\n \"@class\": \"org.springframework.security.oauth2.core.OAuth2AuthenticationException\","
+ "\n \"error\":"
+ "\n {"
+ "\n \"@class\":\"org.springframework.security.oauth2.core.OAuth2Error\","
+ "\n \"errorCode\":\"" + error.getErrorCode() + "\","
+ "\n \"description\":" + jsonStringOrNull(error.getDescription()) + ","
+ "\n \"uri\":" + jsonStringOrNull(error.getUri())
+ "\n },"
+ "\n \"detailMessage\":" + jsonStringOrNull(exception.getMessage())
+ "\n}";
// @formatter:on
}
private String jsonStringOrNull(String input) {
return (input != null) ? "\"" + input + "\"" : "null";
}
}
@@ -0,0 +1,339 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthenticationTokens;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthenticationTokenMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthenticationTokenMixinTests {
private JsonMapper mapper;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder()
.addModules(SecurityJacksonModules.getModules(loader))
// see https://github.com/FasterXML/jackson-databind/issues/3052 for details
.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS)
.build();
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
// OidcUser
OAuth2AuthenticationToken authentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String expectedJson = asJson(authentication);
String json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
// OAuth2User
authentication = TestOAuth2AuthenticationTokens.authenticated();
expectedJson = asJson(authentication);
json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
DefaultOidcUser principal = TestOidcUsers.create();
principal = new DefaultOidcUser(principal.getAuthorities(), principal.getIdToken());
OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(principal, Collections.emptyList(),
"registration-id");
String expectedJson = asJson(authentication);
String json = this.mapper.writeValueAsString(authentication);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
OAuth2AuthenticationToken authentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String json = asJson(authentication);
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthenticationToken.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
// OidcUser
OAuth2AuthenticationToken expectedAuthentication = TestOAuth2AuthenticationTokens.oidcAuthenticated();
String json = asJson(expectedAuthentication);
OAuth2AuthenticationToken authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOidcUser expectedOidcUser = (DefaultOidcUser) expectedAuthentication.getPrincipal();
DefaultOidcUser oidcUser = (DefaultOidcUser) authentication.getPrincipal();
assertThat(oidcUser.getAuthorities().containsAll(expectedOidcUser.getAuthorities())).isTrue();
assertThat(oidcUser.getAttributes()).containsExactlyEntriesOf(expectedOidcUser.getAttributes());
assertThat(oidcUser.getName()).isEqualTo(expectedOidcUser.getName());
OidcIdToken expectedIdToken = expectedOidcUser.getIdToken();
OidcIdToken idToken = oidcUser.getIdToken();
assertThat(idToken.getTokenValue()).isEqualTo(expectedIdToken.getTokenValue());
assertThat(idToken.getIssuedAt()).isEqualTo(expectedIdToken.getIssuedAt());
assertThat(idToken.getExpiresAt()).isEqualTo(expectedIdToken.getExpiresAt());
assertThat(idToken.getClaims()).containsExactlyEntriesOf(expectedIdToken.getClaims());
OidcUserInfo expectedUserInfo = expectedOidcUser.getUserInfo();
OidcUserInfo userInfo = oidcUser.getUserInfo();
assertThat(userInfo.getClaims()).containsExactlyEntriesOf(expectedUserInfo.getClaims());
// OAuth2User
expectedAuthentication = TestOAuth2AuthenticationTokens.authenticated();
json = asJson(expectedAuthentication);
authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).containsExactlyElementsOf(expectedAuthentication.getAuthorities());
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOAuth2User expectedOauth2User = (DefaultOAuth2User) expectedAuthentication.getPrincipal();
DefaultOAuth2User oauth2User = (DefaultOAuth2User) authentication.getPrincipal();
assertThat(oauth2User.getAuthorities().containsAll(expectedOauth2User.getAuthorities())).isTrue();
assertThat(oauth2User.getAttributes()).containsExactlyEntriesOf(expectedOauth2User.getAttributes());
assertThat(oauth2User.getName()).isEqualTo(expectedOauth2User.getName());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
DefaultOidcUser expectedPrincipal = TestOidcUsers.create();
expectedPrincipal = new DefaultOidcUser(expectedPrincipal.getAuthorities(), expectedPrincipal.getIdToken());
OAuth2AuthenticationToken expectedAuthentication = new OAuth2AuthenticationToken(expectedPrincipal,
Collections.emptyList(), "registration-id");
String json = asJson(expectedAuthentication);
OAuth2AuthenticationToken authentication = this.mapper.readValue(json, OAuth2AuthenticationToken.class);
assertThat(authentication.getAuthorities()).isEmpty();
assertThat(authentication.getDetails()).isEqualTo(expectedAuthentication.getDetails());
assertThat(authentication.isAuthenticated()).isEqualTo(expectedAuthentication.isAuthenticated());
assertThat(authentication.getAuthorizedClientRegistrationId())
.isEqualTo(expectedAuthentication.getAuthorizedClientRegistrationId());
DefaultOidcUser principal = (DefaultOidcUser) authentication.getPrincipal();
assertThat(principal.getAuthorities().containsAll(expectedPrincipal.getAuthorities())).isTrue();
assertThat(principal.getAttributes()).containsExactlyEntriesOf(expectedPrincipal.getAttributes());
assertThat(principal.getName()).isEqualTo(expectedPrincipal.getName());
OidcIdToken expectedIdToken = expectedPrincipal.getIdToken();
OidcIdToken idToken = principal.getIdToken();
assertThat(idToken.getTokenValue()).isEqualTo(expectedIdToken.getTokenValue());
assertThat(idToken.getIssuedAt()).isEqualTo(expectedIdToken.getIssuedAt());
assertThat(idToken.getExpiresAt()).isEqualTo(expectedIdToken.getExpiresAt());
assertThat(idToken.getClaims()).containsExactlyEntriesOf(expectedIdToken.getClaims());
assertThat(principal.getUserInfo()).isNull();
}
private static String asJson(OAuth2AuthenticationToken authentication) {
String principalJson = (authentication.getPrincipal() instanceof DefaultOidcUser)
? asJson((DefaultOidcUser) authentication.getPrincipal())
: asJson((DefaultOAuth2User) authentication.getPrincipal());
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken\",\n" +
" \"principal\": " + principalJson + ",\n" +
" \"authorities\": " + asJson(authentication.getAuthorities(), "java.util.Collections$UnmodifiableRandomAccessList") + ",\n" +
" \"authorizedClientRegistrationId\": \"" + authentication.getAuthorizedClientRegistrationId() + "\",\n" +
" \"details\": null\n" +
"}";
// @formatter:on
}
private static String asJson(DefaultOAuth2User oauth2User) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.user.DefaultOAuth2User\",\n" +
" \"authorities\": " + asJson(oauth2User.getAuthorities(), "java.util.Collections$UnmodifiableSet") + ",\n" +
" \"attributes\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"username\": \"user\"\n" +
" },\n" +
" \"nameAttributeKey\": \"username\"\n" +
" }";
// @formatter:on
}
private static String asJson(DefaultOidcUser oidcUser) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser\",\n" +
" \"authorities\": " + asJson(oidcUser.getAuthorities(), "java.util.Collections$UnmodifiableSet") + ",\n" +
" \"idToken\": " + asJson(oidcUser.getIdToken()) + ",\n" +
" \"userInfo\": " + asJson(oidcUser.getUserInfo()) + ",\n" +
" \"nameAttributeKey\": \"" + IdTokenClaimNames.SUB + "\"\n" +
" }";
// @formatter:on
}
private static String asJson(Collection<? extends GrantedAuthority> authorities, String classTypeInfo) {
OAuth2UserAuthority oauth2UserAuthority = null;
OidcUserAuthority oidcUserAuthority = null;
List<SimpleGrantedAuthority> simpleAuthorities = new ArrayList<>();
for (GrantedAuthority authority : authorities) {
if (authority instanceof OidcUserAuthority) {
oidcUserAuthority = (OidcUserAuthority) authority;
}
else if (authority instanceof OAuth2UserAuthority) {
oauth2UserAuthority = (OAuth2UserAuthority) authority;
}
else if (authority instanceof SimpleGrantedAuthority) {
simpleAuthorities.add((SimpleGrantedAuthority) authority);
}
}
String authoritiesJson = (oidcUserAuthority != null) ? asJson(oidcUserAuthority)
: (oauth2UserAuthority != null) ? asJson(oauth2UserAuthority) : "";
if (!simpleAuthorities.isEmpty()) {
if (StringUtils.hasLength(authoritiesJson)) {
authoritiesJson += ",";
}
authoritiesJson += asJson(simpleAuthorities);
}
// @formatter:off
return "[\n" +
" \"" + classTypeInfo + "\",\n" +
" [" + authoritiesJson + "]\n" +
" ]";
// @formatter:on
}
private static String asJson(OAuth2UserAuthority oauth2UserAuthority) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.user.OAuth2UserAuthority\",\n" +
" \"authority\": \"" + oauth2UserAuthority.getAuthority() + "\",\n" +
" \"userNameAttributeName\": \"username\",\n" +
" \"attributes\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"username\": \"user\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String asJson(OidcUserAuthority oidcUserAuthority) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority\",\n" +
" \"authority\": \"" + oidcUserAuthority.getAuthority() + "\",\n" +
" \"userNameAttributeName\": \"" + oidcUserAuthority.getUserNameAttributeName() + "\",\n" +
" \"idToken\": " + asJson(oidcUserAuthority.getIdToken()) + ",\n" +
" \"userInfo\": " + asJson(oidcUserAuthority.getUserInfo()) + "\n" +
" }";
// @formatter:on
}
private static String asJson(List<SimpleGrantedAuthority> simpleAuthorities) {
// @formatter:off
return simpleAuthorities.stream()
.map((authority) -> "{\n" +
" \"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\",\n" +
" \"authority\": \"" + authority.getAuthority() + "\"\n" +
" }")
.collect(Collectors.joining(","));
// @formatter:on
}
private static String asJson(OidcIdToken idToken) {
String aud = "";
if (!CollectionUtils.isEmpty(idToken.getAudience())) {
aud = StringUtils.collectionToDelimitedString(idToken.getAudience(), ",", "\"", "\"");
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.OidcIdToken\",\n" +
" \"tokenValue\": \"" + idToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(idToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(idToken.getExpiresAt()) + ",\n" +
" \"claims\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"iat\": [\n" +
" \"java.time.Instant\",\n" +
" " + toString(idToken.getIssuedAt()) + "\n" +
" ],\n" +
" \"exp\": [\n" +
" \"java.time.Instant\",\n" +
" " + toString(idToken.getExpiresAt()) + "\n" +
" ],\n" +
" \"sub\": \"" + idToken.getSubject() + "\",\n" +
" \"iss\": \"" + idToken.getIssuer() + "\",\n" +
" \"aud\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + aud + "]\n" +
" ],\n" +
" \"azp\": \"" + idToken.getAuthorizedParty() + "\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String asJson(OidcUserInfo userInfo) {
if (userInfo == null) {
return null;
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.oidc.OidcUserInfo\",\n" +
" \"claims\": {\n" +
" \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" +
" \"sub\": \"" + userInfo.getSubject() + "\",\n" +
" \"name\": \"" + userInfo.getClaim(StandardClaimNames.NAME) + "\"\n" +
" }\n" +
" }";
// @formatter:on
}
private static String toString(Instant instant) {
if (instant == null) {
return null;
}
return DecimalUtils.toBigDecimal(instant.getEpochSecond(), instant.getNano()).toString();
}
}
@@ -0,0 +1,199 @@
/*
* 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.oauth2.client.jackson;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthorizationRequestMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthorizationRequestMixinTests {
private JsonMapper mapper;
private OAuth2AuthorizationRequest.Builder authorizationRequestBuilder;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
Map<String, Object> additionalParameters = new LinkedHashMap<>();
additionalParameters.put("param1", "value1");
additionalParameters.put("param2", "value2");
// @formatter:off
this.authorizationRequestBuilder = TestOAuth2AuthorizationRequests.request()
.scope("read", "write")
.additionalParameters(additionalParameters);
// @formatter:on
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder.build();
String expectedJson = asJson(authorizationRequest);
String json = this.mapper.writeValueAsString(authorizationRequest);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
// @formatter:off
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder
.scopes(null)
.state(null)
.additionalParameters(Map::clear)
.attributes(Map::clear)
.build();
// @formatter:on
String expectedJson = asJson(authorizationRequest);
String json = this.mapper.writeValueAsString(authorizationRequest);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
String json = asJson(this.authorizationRequestBuilder.build());
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthorizationRequest.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
OAuth2AuthorizationRequest expectedAuthorizationRequest = this.authorizationRequestBuilder.build();
String json = asJson(expectedAuthorizationRequest);
OAuth2AuthorizationRequest authorizationRequest = this.mapper.readValue(json, OAuth2AuthorizationRequest.class);
assertThat(authorizationRequest.getAuthorizationUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationUri());
assertThat(authorizationRequest.getGrantType()).isEqualTo(expectedAuthorizationRequest.getGrantType());
assertThat(authorizationRequest.getResponseType()).isEqualTo(expectedAuthorizationRequest.getResponseType());
assertThat(authorizationRequest.getClientId()).isEqualTo(expectedAuthorizationRequest.getClientId());
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedAuthorizationRequest.getRedirectUri());
assertThat(authorizationRequest.getScopes()).isEqualTo(expectedAuthorizationRequest.getScopes());
assertThat(authorizationRequest.getState()).isEqualTo(expectedAuthorizationRequest.getState());
assertThat(authorizationRequest.getAdditionalParameters())
.containsExactlyEntriesOf(expectedAuthorizationRequest.getAdditionalParameters());
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationRequestUri());
assertThat(authorizationRequest.getAttributes())
.containsExactlyEntriesOf(expectedAuthorizationRequest.getAttributes());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
// @formatter:off
OAuth2AuthorizationRequest expectedAuthorizationRequest = this.authorizationRequestBuilder.scopes(null)
.state(null)
.additionalParameters(Map::clear)
.attributes(Map::clear)
.build();
// @formatter:on
String json = asJson(expectedAuthorizationRequest);
OAuth2AuthorizationRequest authorizationRequest = this.mapper.readValue(json, OAuth2AuthorizationRequest.class);
assertThat(authorizationRequest.getAuthorizationUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationUri());
assertThat(authorizationRequest.getGrantType()).isEqualTo(expectedAuthorizationRequest.getGrantType());
assertThat(authorizationRequest.getResponseType()).isEqualTo(expectedAuthorizationRequest.getResponseType());
assertThat(authorizationRequest.getClientId()).isEqualTo(expectedAuthorizationRequest.getClientId());
assertThat(authorizationRequest.getRedirectUri()).isEqualTo(expectedAuthorizationRequest.getRedirectUri());
assertThat(authorizationRequest.getScopes()).isEmpty();
assertThat(authorizationRequest.getState()).isNull();
assertThat(authorizationRequest.getAdditionalParameters()).isEmpty();
assertThat(authorizationRequest.getAuthorizationRequestUri())
.isEqualTo(expectedAuthorizationRequest.getAuthorizationRequestUri());
assertThat(authorizationRequest.getAttributes()).isEmpty();
}
@Test
public void deserializeWhenInvalidAuthorizationGrantTypeThenThrowJsonParseException() {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestBuilder.build();
String json = asJson(authorizationRequest).replace("authorization_code", "client_credentials");
assertThatExceptionOfType(StreamReadException.class)
.isThrownBy(() -> this.mapper.readValue(json, OAuth2AuthorizationRequest.class))
.withMessageContaining("Invalid authorizationGrantType");
}
private static String asJson(OAuth2AuthorizationRequest authorizationRequest) {
String scopes = "";
if (!CollectionUtils.isEmpty(authorizationRequest.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(authorizationRequest.getScopes(), ",", "\"", "\"");
}
String additionalParameters = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(authorizationRequest.getAdditionalParameters())) {
additionalParameters += "," + authorizationRequest.getAdditionalParameters()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + authorizationRequest.getAdditionalParameters().get(key) + "\"")
.collect(Collectors.joining(","));
}
String attributes = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(authorizationRequest.getAttributes())) {
attributes += "," + authorizationRequest.getAttributes()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + authorizationRequest.getAttributes().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest\",\n" +
" \"authorizationUri\": \"" + authorizationRequest.getAuthorizationUri() + "\",\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + authorizationRequest.getGrantType().getValue() + "\"\n" +
" },\n" +
" \"responseType\": {\n" +
" \"value\": \"" + authorizationRequest.getResponseType().getValue() + "\"\n" +
" },\n" +
" \"clientId\": \"" + authorizationRequest.getClientId() + "\",\n" +
" \"redirectUri\": \"" + authorizationRequest.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"state\": " + ((authorizationRequest.getState() != null) ? "\"" + authorizationRequest.getState() + "\"" : "null") + ",\n" +
" \"additionalParameters\": {\n" +
" " + additionalParameters + "\n" +
" },\n" +
" \"authorizationRequestUri\": \"" + authorizationRequest.getAuthorizationRequestUri() + "\",\n" +
" \"attributes\": {\n" +
" " + attributes + "\n" +
" }\n" +
"}";
// @formatter:on
}
}
@@ -0,0 +1,395 @@
/*
* 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.oauth2.client.jackson;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import com.fasterxml.jackson.datatype.jsr310.DecimalUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.security.jackson.SecurityJacksonModules;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for
* {@link org.springframework.security.oauth2.client.jackson.OAuth2AuthorizedClientMixin}.
*
* @author Joe Grandja
*/
public class OAuth2AuthorizedClientMixinTests {
private JsonMapper mapper;
private ClientRegistration.Builder clientRegistrationBuilder;
private OAuth2AccessToken accessToken;
private OAuth2RefreshToken refreshToken;
private String principalName;
@BeforeEach
public void setup() {
ClassLoader loader = getClass().getClassLoader();
this.mapper = JsonMapper.builder().addModules(SecurityJacksonModules.getModules(loader)).build();
Map<String, Object> providerConfigurationMetadata = new LinkedHashMap<>();
providerConfigurationMetadata.put("config1", "value1");
providerConfigurationMetadata.put("config2", "value2");
// @formatter:off
this.clientRegistrationBuilder = TestClientRegistrations.clientRegistration()
.authorizationGrantType(new AuthorizationGrantType("custom-grant"))
.scope("read", "write")
.providerConfigurationMetadata(providerConfigurationMetadata);
// @formatter:on
this.accessToken = TestOAuth2AccessTokens.scopes("read", "write");
this.refreshToken = TestOAuth2RefreshTokens.refreshToken();
this.principalName = "principal-name";
}
@Test
public void serializeWhenMixinRegisteredThenSerializes() throws Exception {
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistrationBuilder.build(),
this.principalName, this.accessToken, this.refreshToken);
String expectedJson = asJson(authorizedClient);
String json = this.mapper.writeValueAsString(authorizedClient);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void serializeWhenRequiredAttributesOnlyThenSerializes() throws Exception {
// @formatter:off
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
.clientSecret(null)
.clientName(null)
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
.issuerUri(null)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, this.principalName,
TestOAuth2AccessTokens.noScopes());
String expectedJson = asJson(authorizedClient);
String json = this.mapper.writeValueAsString(authorizedClient);
JSONAssert.assertEquals(expectedJson, json, true);
}
@Test
public void deserializeWhenMixinNotRegisteredThenThrowJsonProcessingException() {
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(this.clientRegistrationBuilder.build(),
this.principalName, this.accessToken);
String json = asJson(authorizedClient);
assertThatExceptionOfType(JacksonException.class)
.isThrownBy(() -> new JsonMapper().readValue(json, OAuth2AuthorizedClient.class));
}
@Test
public void deserializeWhenMixinRegisteredThenDeserializes() throws Exception {
ClientRegistration expectedClientRegistration = this.clientRegistrationBuilder.build();
OAuth2AccessToken expectedAccessToken = this.accessToken;
OAuth2RefreshToken expectedRefreshToken = this.refreshToken;
OAuth2AuthorizedClient expectedAuthorizedClient = new OAuth2AuthorizedClient(expectedClientRegistration,
this.principalName, expectedAccessToken, expectedRefreshToken);
String json = asJson(expectedAuthorizedClient);
OAuth2AuthorizedClient authorizedClient = this.mapper.readValue(json, OAuth2AuthorizedClient.class);
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
assertThat(clientRegistration.getRegistrationId()).isEqualTo(expectedClientRegistration.getRegistrationId());
assertThat(clientRegistration.getClientId()).isEqualTo(expectedClientRegistration.getClientId());
assertThat(clientRegistration.getClientSecret()).isEqualTo(expectedClientRegistration.getClientSecret());
assertThat(clientRegistration.getClientAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getClientAuthenticationMethod());
assertThat(clientRegistration.getAuthorizationGrantType())
.isEqualTo(expectedClientRegistration.getAuthorizationGrantType());
assertThat(clientRegistration.getRedirectUri()).isEqualTo(expectedClientRegistration.getRedirectUri());
assertThat(clientRegistration.getScopes()).isEqualTo(expectedClientRegistration.getScopes());
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getAuthorizationUri());
assertThat(clientRegistration.getProviderDetails().getTokenUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getTokenUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(
expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
assertThat(clientRegistration.getProviderDetails().getJwkSetUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getJwkSetUri());
assertThat(clientRegistration.getProviderDetails().getIssuerUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getIssuerUri());
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata())
.containsExactlyEntriesOf(clientRegistration.getProviderDetails().getConfigurationMetadata());
assertThat(clientRegistration.getClientName()).isEqualTo(expectedClientRegistration.getClientName());
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expectedAuthorizedClient.getPrincipalName());
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
assertThat(accessToken.getTokenType()).isEqualTo(expectedAccessToken.getTokenType());
assertThat(accessToken.getScopes()).isEqualTo(expectedAccessToken.getScopes());
assertThat(accessToken.getTokenValue()).isEqualTo(expectedAccessToken.getTokenValue());
assertThat(accessToken.getIssuedAt()).isEqualTo(expectedAccessToken.getIssuedAt());
assertThat(accessToken.getExpiresAt()).isEqualTo(expectedAccessToken.getExpiresAt());
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
assertThat(refreshToken.getTokenValue()).isEqualTo(expectedRefreshToken.getTokenValue());
assertThat(refreshToken.getIssuedAt()).isEqualTo(expectedRefreshToken.getIssuedAt());
assertThat(refreshToken.getExpiresAt()).isEqualTo(expectedRefreshToken.getExpiresAt());
}
@Test
public void deserializeWhenRequiredAttributesOnlyThenDeserializes() throws Exception {
// @formatter:off
ClientRegistration expectedClientRegistration = TestClientRegistrations.clientRegistration()
.clientSecret(null)
.clientName(null)
.userInfoUri(null)
.userNameAttributeName(null)
.jwkSetUri(null)
.issuerUri(null)
.build();
// @formatter:on
OAuth2AccessToken expectedAccessToken = TestOAuth2AccessTokens.noScopes();
OAuth2AuthorizedClient expectedAuthorizedClient = new OAuth2AuthorizedClient(expectedClientRegistration,
this.principalName, expectedAccessToken);
String json = asJson(expectedAuthorizedClient);
OAuth2AuthorizedClient authorizedClient = this.mapper.readValue(json, OAuth2AuthorizedClient.class);
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
assertThat(clientRegistration.getRegistrationId()).isEqualTo(expectedClientRegistration.getRegistrationId());
assertThat(clientRegistration.getClientId()).isEqualTo(expectedClientRegistration.getClientId());
assertThat(clientRegistration.getClientSecret()).isEmpty();
assertThat(clientRegistration.getClientAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getClientAuthenticationMethod());
assertThat(clientRegistration.getAuthorizationGrantType())
.isEqualTo(expectedClientRegistration.getAuthorizationGrantType());
assertThat(clientRegistration.getRedirectUri()).isEqualTo(expectedClientRegistration.getRedirectUri());
assertThat(clientRegistration.getScopes()).isEqualTo(expectedClientRegistration.getScopes());
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getAuthorizationUri());
assertThat(clientRegistration.getProviderDetails().getTokenUri())
.isEqualTo(expectedClientRegistration.getProviderDetails().getTokenUri());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())
.isEqualTo(expectedClientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod());
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).isNull();
assertThat(clientRegistration.getProviderDetails().getJwkSetUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNull();
assertThat(clientRegistration.getProviderDetails().getConfigurationMetadata()).isEmpty();
assertThat(clientRegistration.getClientName()).isEqualTo(clientRegistration.getRegistrationId());
assertThat(authorizedClient.getPrincipalName()).isEqualTo(expectedAuthorizedClient.getPrincipalName());
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
assertThat(accessToken.getTokenType()).isEqualTo(expectedAccessToken.getTokenType());
assertThat(accessToken.getScopes()).isEmpty();
assertThat(accessToken.getTokenValue()).isEqualTo(expectedAccessToken.getTokenValue());
assertThat(accessToken.getIssuedAt()).isEqualTo(expectedAccessToken.getIssuedAt());
assertThat(accessToken.getExpiresAt()).isEqualTo(expectedAccessToken.getExpiresAt());
assertThat(authorizedClient.getRefreshToken()).isNull();
}
@Test
void deserializeWhenClientSettingsPropertyDoesNotExistThenDefaulted() throws JacksonException {
// ClientRegistration.clientSettings was added later, so old values will be
// serialized without that property
// this test checks for passivity
ClientRegistration clientRegistration = this.clientRegistrationBuilder.build();
ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
String scopes = "";
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), ",", "\"", "\"");
}
String configurationMetadata = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(providerDetails.getConfigurationMetadata())) {
configurationMetadata += "," + providerDetails.getConfigurationMetadata()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + providerDetails.getConfigurationMetadata().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
String json = "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration\",\n" +
" \"registrationId\": \"" + clientRegistration.getRegistrationId() + "\",\n" +
" \"clientId\": \"" + clientRegistration.getClientId() + "\",\n" +
" \"clientSecret\": \"" + clientRegistration.getClientSecret() + "\",\n" +
" \"clientAuthenticationMethod\": {\n" +
" \"value\": \"" + clientRegistration.getClientAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + clientRegistration.getAuthorizationGrantType().getValue() + "\"\n" +
" },\n" +
" \"redirectUri\": \"" + clientRegistration.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"providerDetails\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails\",\n" +
" \"authorizationUri\": \"" + providerDetails.getAuthorizationUri() + "\",\n" +
" \"tokenUri\": \"" + providerDetails.getTokenUri() + "\",\n" +
" \"userInfoEndpoint\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails$UserInfoEndpoint\",\n" +
" \"uri\": " + ((userInfoEndpoint.getUri() != null) ? "\"" + userInfoEndpoint.getUri() + "\"" : null) + ",\n" +
" \"authenticationMethod\": {\n" +
" \"value\": \"" + userInfoEndpoint.getAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"userNameAttributeName\": " + ((userInfoEndpoint.getUserNameAttributeName() != null) ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
" },\n" +
" \"jwkSetUri\": " + ((providerDetails.getJwkSetUri() != null) ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
" \"issuerUri\": " + ((providerDetails.getIssuerUri() != null) ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
" \"configurationMetadata\": {\n" +
" " + configurationMetadata + "\n" +
" }\n" +
" },\n" +
" \"clientName\": \"" + clientRegistration.getClientName() + "\"\n" +
"}";
// @formatter:on
// validate the test input
assertThat(json).doesNotContain("clientSettings");
ClientRegistration registration = this.mapper.readValue(json, ClientRegistration.class);
// the default value of requireProofKey is false
assertThat(registration.getClientSettings().isRequireProofKey()).isFalse();
}
private static String asJson(OAuth2AuthorizedClient authorizedClient) {
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.OAuth2AuthorizedClient\",\n" +
" \"clientRegistration\": " + asJson(authorizedClient.getClientRegistration()) + ",\n" +
" \"principalName\": \"" + authorizedClient.getPrincipalName() + "\",\n" +
" \"accessToken\": " + asJson(authorizedClient.getAccessToken()) + ",\n" +
" \"refreshToken\": " + asJson(authorizedClient.getRefreshToken()) + "\n" +
"}";
// @formatter:on
}
private static String asJson(ClientRegistration clientRegistration) {
ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
ClientRegistration.ProviderDetails.UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
String scopes = "";
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(clientRegistration.getScopes(), ",", "\"", "\"");
}
String configurationMetadata = "\"@class\": \"java.util.Collections$UnmodifiableMap\"";
if (!CollectionUtils.isEmpty(providerDetails.getConfigurationMetadata())) {
configurationMetadata += "," + providerDetails.getConfigurationMetadata()
.keySet()
.stream()
.map((key) -> "\"" + key + "\": \"" + providerDetails.getConfigurationMetadata().get(key) + "\"")
.collect(Collectors.joining(","));
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration\",\n" +
" \"registrationId\": \"" + clientRegistration.getRegistrationId() + "\",\n" +
" \"clientId\": \"" + clientRegistration.getClientId() + "\",\n" +
" \"clientSecret\": \"" + clientRegistration.getClientSecret() + "\",\n" +
" \"clientAuthenticationMethod\": {\n" +
" \"value\": \"" + clientRegistration.getClientAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"authorizationGrantType\": {\n" +
" \"value\": \"" + clientRegistration.getAuthorizationGrantType().getValue() + "\"\n" +
" },\n" +
" \"redirectUri\": \"" + clientRegistration.getRedirectUri() + "\",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ],\n" +
" \"providerDetails\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails\",\n" +
" \"authorizationUri\": \"" + providerDetails.getAuthorizationUri() + "\",\n" +
" \"tokenUri\": \"" + providerDetails.getTokenUri() + "\",\n" +
" \"userInfoEndpoint\": {\n" +
" \"@class\": \"org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails$UserInfoEndpoint\",\n" +
" \"uri\": " + ((userInfoEndpoint.getUri() != null) ? "\"" + userInfoEndpoint.getUri() + "\"" : null) + ",\n" +
" \"authenticationMethod\": {\n" +
" \"value\": \"" + userInfoEndpoint.getAuthenticationMethod().getValue() + "\"\n" +
" },\n" +
" \"userNameAttributeName\": " + ((userInfoEndpoint.getUserNameAttributeName() != null) ? "\"" + userInfoEndpoint.getUserNameAttributeName() + "\"" : null) + "\n" +
" },\n" +
" \"jwkSetUri\": " + ((providerDetails.getJwkSetUri() != null) ? "\"" + providerDetails.getJwkSetUri() + "\"" : null) + ",\n" +
" \"issuerUri\": " + ((providerDetails.getIssuerUri() != null) ? "\"" + providerDetails.getIssuerUri() + "\"" : null) + ",\n" +
" \"configurationMetadata\": {\n" +
" " + configurationMetadata + "\n" +
" }\n" +
" },\n" +
" \"clientName\": \"" + clientRegistration.getClientName() + "\",\n" +
" \"clientSettings\": {\n" +
" \"requireProofKey\": " + clientRegistration.getClientSettings().isRequireProofKey() + "\n" +
" }\n" +
"}";
// @formatter:on
}
private static String asJson(OAuth2AccessToken accessToken) {
String scopes = "";
if (!CollectionUtils.isEmpty(accessToken.getScopes())) {
scopes = StringUtils.collectionToDelimitedString(accessToken.getScopes(), ",", "\"", "\"");
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.OAuth2AccessToken\",\n" +
" \"tokenType\": {\n" +
" \"value\": \"" + accessToken.getTokenType().getValue() + "\"\n" +
" },\n" +
" \"tokenValue\": \"" + accessToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(accessToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(accessToken.getExpiresAt()) + ",\n" +
" \"scopes\": [\n" +
" \"java.util.Collections$UnmodifiableSet\",\n" +
" [" + scopes + "]\n" +
" ]\n" +
"}";
// @formatter:on
}
private static String asJson(OAuth2RefreshToken refreshToken) {
if (refreshToken == null) {
return null;
}
// @formatter:off
return "{\n" +
" \"@class\": \"org.springframework.security.oauth2.core.OAuth2RefreshToken\",\n" +
" \"tokenValue\": \"" + refreshToken.getTokenValue() + "\",\n" +
" \"issuedAt\": " + toString(refreshToken.getIssuedAt()) + ",\n" +
" \"expiresAt\": " + toString(refreshToken.getExpiresAt()) + "\n" +
"}";
// @formatter:on
}
private static String toString(Instant instant) {
if (instant == null) {
return null;
}
return DecimalUtils.toBigDecimal(instant.getEpochSecond(), instant.getNano()).toString();
}
}
@@ -0,0 +1,53 @@
/*
* 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.oauth2.client.jackson;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.node.JsonNodeFactory;
import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import static org.assertj.core.api.Assertions.assertThat;
public class StdConvertersTests {
private final StdConverter<JsonNode, ClientAuthenticationMethod> clientAuthenticationMethodConverter = new org.springframework.security.oauth2.client.jackson.StdConverters.ClientAuthenticationMethodConverter();
@ParameterizedTest
@MethodSource("convertWhenClientAuthenticationMethodConvertedThenDeserializes")
void convertWhenClientAuthenticationMethodConvertedThenDeserializes(String clientAuthenticationMethod) {
ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
jsonNode.put("value", clientAuthenticationMethod);
ClientAuthenticationMethod actual = this.clientAuthenticationMethodConverter.convert(jsonNode);
assertThat(actual.getValue()).isEqualTo(clientAuthenticationMethod);
}
static Stream<Arguments> convertWhenClientAuthenticationMethodConvertedThenDeserializes() {
return Stream.of(Arguments.of(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()),
Arguments.of(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()),
Arguments.of(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()),
Arguments.of(ClientAuthenticationMethod.NONE.getValue()), Arguments.of("custom_method"));
}
}
@@ -20,8 +20,6 @@ import java.net.URI;
import java.util.Arrays;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -29,6 +27,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -111,7 +111,7 @@ public class ClientRegistrationsTests {
private MockWebServer server;
private ObjectMapper mapper = new ObjectMapper();
private JsonMapper mapper = new JsonMapper();
private Map<String, Object> response;
@@ -15,7 +15,7 @@ dependencies {
testImplementation "jakarta.servlet:jakarta.servlet-api"
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'tools.jackson.core:jackson-databind'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -21,10 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
@@ -33,6 +29,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -191,8 +189,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponse(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -203,8 +200,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOidc(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -216,8 +212,7 @@ public class JwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOAuth2(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -384,8 +379,8 @@ public class JwtDecodersTests {
// @formatter:on
}
public String buildResponseWithMissingJwksUri() throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
public String buildResponseWithMissingJwksUri() {
JsonMapper mapper = new JsonMapper();
Map<String, Object> response = mapper.readValue(DEFAULT_RESPONSE_TEMPLATE,
new TypeReference<Map<String, Object>>() {
});
@@ -21,10 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
@@ -33,6 +29,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -153,8 +151,7 @@ public class ReactiveJwtDecoderProviderConfigurationUtilsTests {
// gh-7512
@Test
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOidc(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -165,8 +162,7 @@ public class ReactiveJwtDecoderProviderConfigurationUtilsTests {
// gh-7512
@Test
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOAuth2(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -323,8 +319,8 @@ public class ReactiveJwtDecoderProviderConfigurationUtilsTests {
// @formatter:on
}
public String buildResponseWithMissingJwksUri() throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
public String buildResponseWithMissingJwksUri() {
JsonMapper mapper = new JsonMapper();
Map<String, Object> response = mapper.readValue(DEFAULT_RESPONSE_TEMPLATE,
new TypeReference<Map<String, Object>>() {
});
@@ -21,10 +21,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
@@ -33,6 +29,8 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -166,8 +164,7 @@ public class ReactiveJwtDecodersTests {
// gh-7512
@Test
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponse(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -178,8 +175,7 @@ public class ReactiveJwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOidcFallbackResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOidc(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -190,8 +186,7 @@ public class ReactiveJwtDecodersTests {
// gh-7512
@Test
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException()
throws JsonMappingException, JsonProcessingException {
public void issuerWhenOAuth2ResponseDoesNotContainJwksUriThenThrowsIllegalArgumentException() {
prepareConfigurationResponseOAuth2(this.buildResponseWithMissingJwksUri());
// @formatter:off
assertThatIllegalArgumentException()
@@ -357,8 +352,8 @@ public class ReactiveJwtDecodersTests {
// @formatter:on
}
public String buildResponseWithMissingJwksUri() throws JsonMappingException, JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
public String buildResponseWithMissingJwksUri() {
JsonMapper mapper = new JsonMapper();
Map<String, Object> response = mapper.readValue(DEFAULT_RESPONSE_TEMPLATE,
new TypeReference<Map<String, Object>>() {
});
@@ -17,7 +17,7 @@ dependencies {
testImplementation project(path : ':spring-security-core', configuration : 'tests')
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'tools.jackson.core:jackson-databind'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'io.projectreactor:reactor-test'
testImplementation "org.assertj:assertj-core"
@@ -156,6 +156,7 @@ public final class OAuth2ProtectedResourceMetadataFilter extends OncePerRequestF
private HttpMessageConverters() {
}
@SuppressWarnings("removal")
private static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
if (jackson2Present) {
return new MappingJackson2HttpMessageConverter();
@@ -26,13 +26,13 @@ import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.convert.converter.Converter;
@@ -104,7 +104,7 @@ public class SpringReactiveOpaqueTokenIntrospectorTests {
+ " }";
// @formatter:on
private final ObjectMapper mapper = new ObjectMapper();
private final JsonMapper mapper = new JsonMapper();
@Test
public void authenticateWhenActiveTokenThenOk() throws Exception {