From 020890fce21f5238d5bab9d7325c98cb54ab43b6 Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Sun, 19 Oct 2025 21:00:34 +0300 Subject: [PATCH] Use `SmartHttpMessageConverter` over `GenericHttpMessageConverter` where possible Closes: gh-18073 Signed-off-by: Andrey Litvitski --- .../web/server/HttpMessageConverters.java | 20 +++-- .../http/converter/HttpMessageConverters.java | 20 +++-- ...ionServerMetadataHttpMessageConverter.java | 13 +-- ...lientRegistrationHttpMessageConverter.java | 13 +-- ...okenIntrospectionHttpMessageConverter.java | 13 +-- .../http/converter/HttpMessageConverters.java | 20 +++-- ...lientRegistrationHttpMessageConverter.java | 13 +-- ...iderConfigurationHttpMessageConverter.java | 13 +-- .../OidcUserInfoHttpMessageConverter.java | 13 +-- .../web/HttpMessageConverters.java | 20 +++-- ...hedAuthorizationRequestEndpointFilter.java | 10 ++- ...thorizationRequestEndpointFilterTests.java | 9 ++- ...artGenericHttpMessageConverterAdapter.java | 79 +++++++++++++++++++ .../http/converter/HttpMessageConverters.java | 20 +++-- ...cessTokenResponseHttpMessageConverter.java | 13 +-- ...orizationResponseHttpMessageConverter.java | 13 +-- .../OAuth2ErrorHttpMessageConverter.java | 12 +-- ...OAuth2ProtectedResourceMetadataFilter.java | 28 +++++-- 18 files changed, 257 insertions(+), 85 deletions(-) create mode 100644 oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/SmartGenericHttpMessageConverterAdapter.java diff --git a/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java b/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java index 8a85a37182c..84d2ba7d7dd 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java +++ b/config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java @@ -16,11 +16,13 @@ package org.springframework.security.config.web.server; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** @@ -28,10 +30,13 @@ * * @author Joe Grandja * @author luamas + * @author Andrey Litvitski * @since 5.1 */ final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -40,6 +45,8 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -49,15 +56,18 @@ final class HttpMessageConverters { private HttpMessageConverters() { } - static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java index e8db55e819e..6e7e688161e 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java @@ -16,11 +16,13 @@ package org.springframework.security.oauth2.server.authorization.http.converter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** @@ -28,10 +30,13 @@ * * @author Joe Grandja * @author l uamas + * @author Andrey Litvitski * @since 7.0 */ final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -40,6 +45,8 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -49,15 +56,18 @@ final class HttpMessageConverters { private HttpMessageConverters() { } - static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java index 9a9a6608701..6933a971c21 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java @@ -22,16 +22,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; @@ -43,6 +44,7 @@ * 2.0 Authorization Server Metadata Response}. * * @author Daniel Garnier-Moiroux + * @author Andrey Livtitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OAuth2AuthorizationServerMetadata @@ -53,7 +55,7 @@ public class OAuth2AuthorizationServerMetadataHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter(); @@ -75,7 +77,7 @@ protected OAuth2AuthorizationServerMetadata readInternal(Class authorizationServerMetadataParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.authorizationServerMetadataConverter.convert(authorizationServerMetadataParameters); } catch (Exception ex) { @@ -91,8 +93,9 @@ protected void writeInternal(OAuth2AuthorizationServerMetadata authorizationServ try { Map authorizationServerMetadataResponseParameters = this.authorizationServerMetadataParametersConverter .convert(authorizationServerMetadata); - this.jsonMessageConverter.write(authorizationServerMetadataResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(authorizationServerMetadataResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java index d9b27771524..377ce5b2cee 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2ClientRegistrationHttpMessageConverter.java @@ -27,16 +27,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.server.authorization.OAuth2ClientMetadataClaimNames; @@ -50,6 +51,7 @@ * Client Registration Request and Response}. * * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OAuth2ClientRegistration @@ -60,7 +62,7 @@ public class OAuth2ClientRegistrationHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OAuth2ClientRegistration> clientRegistrationConverter = new MapOAuth2ClientRegistrationConverter(); @@ -82,7 +84,7 @@ protected OAuth2ClientRegistration readInternal(Class clientRegistrationParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.clientRegistrationConverter.convert(clientRegistrationParameters); } catch (Exception ex) { @@ -98,8 +100,9 @@ protected void writeInternal(OAuth2ClientRegistration clientRegistration, HttpOu try { Map clientRegistrationParameters = this.clientRegistrationParametersConverter .convert(clientRegistration); - this.jsonMessageConverter.write(clientRegistrationParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(clientRegistrationParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java index 9f553475c26..86f1e79887b 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java @@ -27,16 +27,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; @@ -51,6 +52,7 @@ * * @author Gerardo Roza * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OAuth2TokenIntrospection @@ -61,7 +63,7 @@ public class OAuth2TokenIntrospectionHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OAuth2TokenIntrospection> tokenIntrospectionConverter = new MapOAuth2TokenIntrospectionConverter(); @@ -83,7 +85,7 @@ protected OAuth2TokenIntrospection readInternal(Class tokenIntrospectionParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.tokenIntrospectionConverter.convert(tokenIntrospectionParameters); } catch (Exception ex) { @@ -98,8 +100,9 @@ protected void writeInternal(OAuth2TokenIntrospection tokenIntrospection, HttpOu try { Map tokenIntrospectionResponseParameters = this.tokenIntrospectionParametersConverter .convert(tokenIntrospection); - this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java index e277b3fcdc5..5b03ecf8e8e 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java @@ -16,11 +16,13 @@ package org.springframework.security.oauth2.server.authorization.oidc.http.converter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** @@ -28,10 +30,13 @@ * * @author Joe Grandja * @author l uamas + * @author Andrey Litvitski * @since 7.0 */ final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -40,6 +45,8 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -49,15 +56,18 @@ final class HttpMessageConverters { private HttpMessageConverters() { } - static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java index 37c741d8863..06e524f0ba5 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcClientRegistrationHttpMessageConverter.java @@ -27,16 +27,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.server.authorization.oidc.OidcClientMetadataClaimNames; @@ -51,6 +52,7 @@ * * @author Ovidiu Popa * @author Joe Grandja + * @author Andrey Litvtitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OidcClientRegistration @@ -60,7 +62,7 @@ public class OidcClientRegistrationHttpMessageConverter extends AbstractHttpMess private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OidcClientRegistration> clientRegistrationConverter = new MapOidcClientRegistrationConverter(); @@ -82,7 +84,7 @@ protected OidcClientRegistration readInternal(Class clientRegistrationParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.clientRegistrationConverter.convert(clientRegistrationParameters); } catch (Exception ex) { @@ -97,8 +99,9 @@ protected void writeInternal(OidcClientRegistration clientRegistration, HttpOutp try { Map clientRegistrationParameters = this.clientRegistrationParametersConverter .convert(clientRegistration); - this.jsonMessageConverter.write(clientRegistrationParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(clientRegistrationParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java index 99a138d9c0c..71f7cbaa043 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcProviderConfigurationHttpMessageConverter.java @@ -22,16 +22,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; @@ -43,6 +44,7 @@ * Configuration Response}. * * @author Daniel Garnier-Moiroux + * @author Andrey Litvitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OidcProviderConfiguration @@ -53,7 +55,7 @@ public class OidcProviderConfigurationHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OidcProviderConfiguration> providerConfigurationConverter = new OidcProviderConfigurationConverter(); @@ -75,7 +77,7 @@ protected OidcProviderConfiguration readInternal(Class providerConfigurationParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.providerConfigurationConverter.convert(providerConfigurationParameters); } catch (Exception ex) { @@ -91,8 +93,9 @@ protected void writeInternal(OidcProviderConfiguration providerConfiguration, Ht try { Map providerConfigurationResponseParameters = this.providerConfigurationParametersConverter .convert(providerConfiguration); - this.jsonMessageConverter.write(providerConfigurationResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(providerConfigurationResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java index 86f8867665c..51056736b94 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverter.java @@ -21,16 +21,17 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.converter.ClaimConversionService; import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; @@ -43,6 +44,7 @@ * * @author Ido Salomon * @author Steve Riesenberg + * @author Andrey Litvitski * @since 7.0 * @see AbstractHttpMessageConverter * @see OidcUserInfo @@ -52,7 +54,7 @@ public class OidcUserInfoHttpMessageConverter extends AbstractHttpMessageConvert private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OidcUserInfo> userInfoConverter = new MapOidcUserInfoConverter(); @@ -74,7 +76,7 @@ protected OidcUserInfo readInternal(Class clazz, HttpInp throws HttpMessageNotReadableException { try { Map userInfoParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.userInfoConverter.convert(userInfoParameters); } catch (Exception ex) { @@ -88,8 +90,9 @@ protected void writeInternal(OidcUserInfo oidcUserInfo, HttpOutputMessage output throws HttpMessageNotWritableException { try { Map userInfoResponseParameters = this.userInfoParametersConverter.convert(oidcUserInfo); - this.jsonMessageConverter.write(userInfoResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(userInfoResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java index f246152d958..e4e2663d330 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java @@ -16,21 +16,26 @@ package org.springframework.security.oauth2.server.authorization.web; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** * Utility methods for {@link HttpMessageConverter}'s. * * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 */ final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -39,6 +44,8 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -48,15 +55,18 @@ final class HttpMessageConverters { private HttpMessageConverters() { } - static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java index 7e73dee08f0..6d7f5fd5a61 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilter.java @@ -28,11 +28,12 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.log.LogMessage; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.http.converter.GenericHttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AuthenticationDetailsSource; @@ -59,6 +60,7 @@ * the processing of the OAuth 2.0 Pushed Authorization Request. * * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 * @see AuthenticationManager * @see OAuth2PushedAuthorizationRequestAuthenticationProvider @@ -82,7 +84,7 @@ public final class OAuth2PushedAuthorizationRequestEndpointFilter extends OncePe private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private static final GenericHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters + private static final SmartHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters .getJsonMessageConverter(); private final AuthenticationManager authenticationManager; @@ -218,8 +220,8 @@ private void sendPushedAuthorizationResponse(HttpServletRequest request, HttpSer ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); httpResponse.setStatusCode(HttpStatus.CREATED); - JSON_MESSAGE_CONVERTER.write(pushedAuthorizationResponse, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, httpResponse); + JSON_MESSAGE_CONVERTER.write(pushedAuthorizationResponse, ResolvableType.forType(STRING_OBJECT_MAP.getType()), + MediaType.APPLICATION_JSON, httpResponse, null); } } diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java index fdf3a2fae2c..6c1888519c4 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2PushedAuthorizationRequestEndpointFilterTests.java @@ -30,9 +30,10 @@ import org.mockito.ArgumentCaptor; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.http.HttpStatus; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -74,6 +75,7 @@ * Tests for {@link OAuth2PushedAuthorizationRequestEndpointFilter}. * * @author Joe Grandja + * @author Andrey Litvitski */ public class OAuth2PushedAuthorizationRequestEndpointFilterTests { @@ -85,7 +87,7 @@ public class OAuth2PushedAuthorizationRequestEndpointFilterTests { private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private AuthenticationManager authenticationManager; @@ -486,7 +488,8 @@ private Map readPushedAuthorizationResponse(MockHttpServletRespo }; MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus())); - return (Map) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, httpResponse); + return (Map) this.jsonMessageConverter.read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), + httpResponse, null); } } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/SmartGenericHttpMessageConverterAdapter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/SmartGenericHttpMessageConverterAdapter.java new file mode 100644 index 00000000000..232f0a01b8e --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/SmartGenericHttpMessageConverterAdapter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-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.core; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.jspecify.annotations.Nullable; + +import org.springframework.core.ResolvableType; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.GenericHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; + +/** + * Adapter that exposes a {@link GenericHttpMessageConverter} as a + * {@link SmartHttpMessageConverter}. Delegates read and write operations with support for + * parameterized types. + * + * @param the type of objects to convert + * @author Andrey Litvitski + * @since 7.0.0 + */ +public class SmartGenericHttpMessageConverterAdapter implements SmartHttpMessageConverter { + + private final GenericHttpMessageConverter genericHttpMessageConverter; + + public SmartGenericHttpMessageConverterAdapter(GenericHttpMessageConverter genericHttpMessageConverter) { + this.genericHttpMessageConverter = genericHttpMessageConverter; + } + + @Override + public boolean canRead(ResolvableType type, @Nullable MediaType mediaType) { + return this.genericHttpMessageConverter.canRead(Objects.requireNonNull(type.getRawClass()), mediaType); + } + + @Override + public T read(ResolvableType type, HttpInputMessage inputMessage, @Nullable Map hints) + throws IOException, HttpMessageNotReadableException { + return this.genericHttpMessageConverter.read(type.getType(), null, inputMessage); + } + + @Override + public boolean canWrite(ResolvableType targetType, Class valueClass, @Nullable MediaType mediaType) { + return this.genericHttpMessageConverter.canWrite(Objects.requireNonNull(targetType.getRawClass()), mediaType); + } + + @Override + public void write(T t, ResolvableType type, @Nullable MediaType contentType, HttpOutputMessage outputMessage, + @Nullable Map hints) throws IOException, HttpMessageNotWritableException { + this.genericHttpMessageConverter.write(t, type.getType(), contentType, outputMessage); + } + + @Override + public List getSupportedMediaTypes() { + return this.genericHttpMessageConverter.getSupportedMediaTypes(); + } + +} diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java index 6ed21588017..c726bb2c0b3 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java @@ -16,11 +16,13 @@ package org.springframework.security.oauth2.core.http.converter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.util.ClassUtils; /** @@ -28,10 +30,13 @@ * * @author Joe Grandja * @author luamas + * @author Andrey Litvitski * @since 5.1 */ final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -40,6 +45,8 @@ final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -49,15 +56,18 @@ final class HttpMessageConverters { private HttpMessageConverters() { } - static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java index 3cd184029ac..dad8b1ab718 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java @@ -21,15 +21,16 @@ import java.util.Map; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.endpoint.DefaultMapOAuth2AccessTokenResponseConverter; import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; @@ -40,6 +41,7 @@ * Token Response}. * * @author Joe Grandja + * @author Andrey Litvitski * @since 5.1 * @see AbstractHttpMessageConverter * @see OAuth2AccessTokenResponse @@ -52,7 +54,7 @@ public class OAuth2AccessTokenResponseHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); + private SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); private Converter, OAuth2AccessTokenResponse> accessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter(); @@ -73,7 +75,7 @@ protected OAuth2AccessTokenResponse readInternal(Class tokenResponseParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.accessTokenResponseConverter.convert(tokenResponseParameters); } catch (Exception ex) { @@ -89,8 +91,9 @@ protected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutput try { Map tokenResponseParameters = this.accessTokenResponseParametersConverter .convert(tokenResponse); - this.jsonMessageConverter.write(tokenResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(tokenResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java index 7be1205b7fa..6739bbded3e 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2DeviceAuthorizationResponseHttpMessageConverter.java @@ -26,15 +26,16 @@ import java.util.Set; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.Assert; @@ -46,6 +47,7 @@ * 2.0 Device Authorization Response}. * * @author Steve Riesenberg + * @author Andrey Litvitski * @since 6.1 * @see AbstractHttpMessageConverter * @see OAuth2DeviceAuthorizationResponse @@ -56,7 +58,7 @@ public class OAuth2DeviceAuthorizationResponseHttpMessageConverter private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private final GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters + private final SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters .getJsonMessageConverter(); private Converter, OAuth2DeviceAuthorizationResponse> deviceAuthorizationResponseConverter = new DefaultMapOAuth2DeviceAuthorizationResponseConverter(); @@ -75,7 +77,7 @@ protected OAuth2DeviceAuthorizationResponse readInternal(Class deviceAuthorizationResponseParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.deviceAuthorizationResponseConverter.convert(deviceAuthorizationResponseParameters); } catch (Exception ex) { @@ -92,8 +94,9 @@ protected void writeInternal(OAuth2DeviceAuthorizationResponse deviceAuthorizati try { Map deviceAuthorizationResponseParameters = this.deviceAuthorizationResponseParametersConverter .convert(deviceAuthorizationResponse); - this.jsonMessageConverter.write(deviceAuthorizationResponseParameters, STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, outputMessage); + this.jsonMessageConverter.write(deviceAuthorizationResponseParameters, + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, outputMessage, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java index fca5c344fbe..069bcb36f6b 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java @@ -23,15 +23,16 @@ import java.util.stream.Collectors; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractHttpMessageConverter; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.Assert; @@ -41,6 +42,7 @@ * A {@link HttpMessageConverter} for an {@link OAuth2Error OAuth 2.0 Error}. * * @author Joe Grandja + * @author Andrey Litvitski * @since 5.1 * @see AbstractHttpMessageConverter * @see OAuth2Error @@ -52,7 +54,7 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); + private SmartHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); protected Converter, OAuth2Error> errorConverter = new OAuth2ErrorConverter(); @@ -75,7 +77,7 @@ protected OAuth2Error readInternal(Class clazz, HttpInput // gh-8157: Parse parameter values as Object in order to handle potential JSON // Object and then convert values to String Map errorParameters = (Map) this.jsonMessageConverter - .read(STRING_OBJECT_MAP.getType(), null, inputMessage); + .read(ResolvableType.forType(STRING_OBJECT_MAP.getType()), inputMessage, null); return this.errorConverter.convert(errorParameters.entrySet() .stream() .collect(Collectors.toMap(Map.Entry::getKey, (entry) -> String.valueOf(entry.getValue())))); @@ -91,8 +93,8 @@ protected void writeInternal(OAuth2Error oauth2Error, HttpOutputMessage outputMe throws HttpMessageNotWritableException { try { Map errorParameters = this.errorParametersConverter.convert(oauth2Error); - this.jsonMessageConverter.write(errorParameters, STRING_OBJECT_MAP.getType(), MediaType.APPLICATION_JSON, - outputMessage); + this.jsonMessageConverter.write(errorParameters, ResolvableType.forType(STRING_OBJECT_MAP.getType()), + MediaType.APPLICATION_JSON, outputMessage, null); } catch (Exception ex) { throw new HttpMessageNotWritableException( diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/OAuth2ProtectedResourceMetadataFilter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/OAuth2ProtectedResourceMetadataFilter.java index 66948bccf60..a01fae9a053 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/OAuth2ProtectedResourceMetadataFilter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/OAuth2ProtectedResourceMetadataFilter.java @@ -26,14 +26,17 @@ import jakarta.servlet.http.HttpServletResponse; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; -import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.security.oauth2.core.SmartGenericHttpMessageConverterAdapter; import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.util.UrlUtils; @@ -48,6 +51,7 @@ * A {@code Filter} that processes OAuth 2.0 Protected Resource Metadata Requests. * * @author Joe Grandja + * @author Andrey Litvitski * @since 7.0 * @see OAuth2ProtectedResourceMetadata * @see > STRING_OBJECT_MAP = new ParameterizedTypeReference<>() { }; - private static final GenericHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters + private static final SmartHttpMessageConverter JSON_MESSAGE_CONVERTER = HttpMessageConverters .getJsonMessageConverter(); /** @@ -108,8 +112,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - JSON_MESSAGE_CONVERTER.write(protectedResourceMetadata.getClaims(), STRING_OBJECT_MAP.getType(), - MediaType.APPLICATION_JSON, httpResponse); + JSON_MESSAGE_CONVERTER.write(protectedResourceMetadata.getClaims(), + ResolvableType.forType(STRING_OBJECT_MAP.getType()), MediaType.APPLICATION_JSON, httpResponse, + null); } catch (Exception ex) { throw new HttpMessageNotWritableException( @@ -139,6 +144,8 @@ private static String resolveResourceIdentifier(HttpServletRequest request) { private static final class HttpMessageConverters { + private static final boolean jackson3Present; + private static final boolean jackson2Present; private static final boolean gsonPresent; @@ -147,6 +154,8 @@ private static final class HttpMessageConverters { static { ClassLoader classLoader = HttpMessageConverters.class.getClassLoader(); + jackson3Present = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader) + && ClassUtils.isPresent("tools.jackson.core.JsonGenerator", classLoader); jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader); @@ -156,15 +165,18 @@ private static final class HttpMessageConverters { private HttpMessageConverters() { } - private static GenericHttpMessageConverter getJsonMessageConverter() { + static SmartHttpMessageConverter getJsonMessageConverter() { + if (jackson3Present) { + return new JacksonJsonHttpMessageConverter(); + } if (jackson2Present) { - return new MappingJackson2HttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new MappingJackson2HttpMessageConverter()); } if (gsonPresent) { - return new GsonHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new GsonHttpMessageConverter()); } if (jsonbPresent) { - return new JsonbHttpMessageConverter(); + return new SmartGenericHttpMessageConverterAdapter<>(new JsonbHttpMessageConverter()); } return null; }