Skip to content

Commit 58da2f0

Browse files
committed
Add support for JacksonJsonHttpMessageConverter
This commit introduces classpath checks and instantiation of JacksonJsonHttpMessageConverter (based on Jackson 3) leveraging a new GenericHttpMessageConverterAdapter which allows to adapt SmartHttpMessageConverter to GenericHttpMessageConverter. Closes spring-projectsgh-17832 Signed-off-by: Sébastien Deleuze <[email protected]>
1 parent 9e1b095 commit 58da2f0

File tree

13 files changed

+277
-13
lines changed

13 files changed

+277
-13
lines changed

config/src/main/java/org/springframework/security/config/web/server/HttpMessageConverters.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import org.springframework.http.converter.GenericHttpMessageConverter;
2020
import org.springframework.http.converter.HttpMessageConverter;
2121
import org.springframework.http.converter.json.GsonHttpMessageConverter;
22+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2223
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2324
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
25+
import org.springframework.security.oauth2.core.http.converter.GenericHttpMessageConverterAdapter;
2426
import org.springframework.util.ClassUtils;
2527

2628
/**
@@ -32,6 +34,8 @@
3234
*/
3335
final class HttpMessageConverters {
3436

37+
private static final boolean jacksonPresent;
38+
3539
private static final boolean jackson2Present;
3640

3741
private static final boolean gsonPresent;
@@ -40,6 +44,7 @@ final class HttpMessageConverters {
4044

4145
static {
4246
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
47+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
4348
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
4449
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
4550
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -50,6 +55,9 @@ private HttpMessageConverters() {
5055
}
5156

5257
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
58+
if (jacksonPresent) {
59+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
60+
}
5361
if (jackson2Present) {
5462
return new MappingJackson2HttpMessageConverter();
5563
}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/HttpMessageConverters.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import org.springframework.http.converter.GenericHttpMessageConverter;
2020
import org.springframework.http.converter.HttpMessageConverter;
2121
import org.springframework.http.converter.json.GsonHttpMessageConverter;
22+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2223
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2324
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
25+
import org.springframework.security.web.http.GenericHttpMessageConverterAdapter;
2426
import org.springframework.util.ClassUtils;
2527

2628
/**
@@ -32,6 +34,8 @@
3234
*/
3335
final class HttpMessageConverters {
3436

37+
private static final boolean jacksonPresent;
38+
3539
private static final boolean jackson2Present;
3640

3741
private static final boolean gsonPresent;
@@ -40,6 +44,7 @@ final class HttpMessageConverters {
4044

4145
static {
4246
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
47+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
4348
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
4449
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
4550
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -50,6 +55,9 @@ private HttpMessageConverters() {
5055
}
5156

5257
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
58+
if (jacksonPresent) {
59+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
60+
}
5361
if (jackson2Present) {
5462
return new MappingJackson2HttpMessageConverter();
5563
}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/HttpMessageConverters.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,23 @@
1919
import org.springframework.http.converter.GenericHttpMessageConverter;
2020
import org.springframework.http.converter.HttpMessageConverter;
2121
import org.springframework.http.converter.json.GsonHttpMessageConverter;
22+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2223
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2324
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
25+
import org.springframework.security.web.http.GenericHttpMessageConverterAdapter;
2426
import org.springframework.util.ClassUtils;
2527

2628
/**
2729
* Utility methods for {@link HttpMessageConverter}'s.
2830
*
2931
* @author Joe Grandja
30-
* @author l uamas
32+
* @author luamas
3133
* @since 7.0
3234
*/
3335
final class HttpMessageConverters {
3436

37+
private static final boolean jacksonPresent;
38+
3539
private static final boolean jackson2Present;
3640

3741
private static final boolean gsonPresent;
@@ -40,6 +44,7 @@ final class HttpMessageConverters {
4044

4145
static {
4246
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
47+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
4348
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
4449
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
4550
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -50,6 +55,9 @@ private HttpMessageConverters() {
5055
}
5156

5257
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
58+
if (jacksonPresent) {
59+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
60+
}
5361
if (jackson2Present) {
5462
return new MappingJackson2HttpMessageConverter();
5563
}

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/HttpMessageConverters.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import org.springframework.http.converter.GenericHttpMessageConverter;
2020
import org.springframework.http.converter.HttpMessageConverter;
2121
import org.springframework.http.converter.json.GsonHttpMessageConverter;
22+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2223
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2324
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
25+
import org.springframework.security.web.http.GenericHttpMessageConverterAdapter;
2426
import org.springframework.util.ClassUtils;
2527

2628
/**
@@ -31,6 +33,8 @@
3133
*/
3234
final class HttpMessageConverters {
3335

36+
private static final boolean jacksonPresent;
37+
3438
private static final boolean jackson2Present;
3539

3640
private static final boolean gsonPresent;
@@ -39,6 +43,7 @@ final class HttpMessageConverters {
3943

4044
static {
4145
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
46+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
4247
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
4348
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
4449
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -49,6 +54,9 @@ private HttpMessageConverters() {
4954
}
5055

5156
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
57+
if (jacksonPresent) {
58+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
59+
}
5260
if (jackson2Present) {
5361
return new MappingJackson2HttpMessageConverter();
5462
}

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/http/converter/OidcUserInfoHttpMessageConverterTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void readInternalWhenValidParametersThenSuccess() {
9090
" \"postal_code\": \"75007\",\n" +
9191
" \"country\": \"France\"\n" +
9292
" },\n" +
93-
" \"updated_at\": 1607633867\n" +
93+
" \"updated_at\": \"2020-12-10T20:57:47Z\"\n" +
9494
"}\n";
9595
// @formatter:on
9696

@@ -178,7 +178,7 @@ public void writeInternalWhenOidcUserInfoThenSuccess() {
178178
assertThat(userInfoResponse).contains("\"address\":");
179179
assertThat(userInfoResponse)
180180
.contains("\"formatted\":\"Champ de Mars\\n5 Av. Anatole France\\n75007 Paris\\nFrance\"");
181-
assertThat(userInfoResponse).contains("\"updated_at\":1607633867");
181+
assertThat(userInfoResponse).contains("\"updated_at\":\"2020-12-10T20:57:47Z\"");
182182
assertThat(userInfoResponse).contains("\"custom_claim\":\"value\"");
183183
assertThat(userInfoResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
184184
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright 2004-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.core.http.converter;
18+
19+
import java.io.IOException;
20+
import java.lang.reflect.Type;
21+
import java.util.List;
22+
23+
import org.jspecify.annotations.Nullable;
24+
25+
import org.springframework.core.ResolvableType;
26+
import org.springframework.http.HttpInputMessage;
27+
import org.springframework.http.HttpOutputMessage;
28+
import org.springframework.http.MediaType;
29+
import org.springframework.http.converter.GenericHttpMessageConverter;
30+
import org.springframework.http.converter.HttpMessageNotReadableException;
31+
import org.springframework.http.converter.HttpMessageNotWritableException;
32+
import org.springframework.http.converter.SmartHttpMessageConverter;
33+
34+
/**
35+
* {@link GenericHttpMessageConverter} implementation that delegates to a
36+
* {@link SmartHttpMessageConverter}.
37+
*
38+
* @param <T> the converted object type
39+
* @author Sebastien Deleuze
40+
* @since 7.0
41+
*/
42+
public class GenericHttpMessageConverterAdapter<T> implements GenericHttpMessageConverter<T> {
43+
44+
private final SmartHttpMessageConverter<T> smartConverter;
45+
46+
public GenericHttpMessageConverterAdapter(SmartHttpMessageConverter<T> smartConverter) {
47+
this.smartConverter = smartConverter;
48+
}
49+
50+
@Override
51+
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
52+
return this.smartConverter.canRead(ResolvableType.forType(type), mediaType);
53+
}
54+
55+
@Override
56+
public T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
57+
throws IOException, HttpMessageNotReadableException {
58+
return this.smartConverter.read(ResolvableType.forType(type), inputMessage, null);
59+
}
60+
61+
@Override
62+
public boolean canWrite(@Nullable Type type, Class<?> clazz, @Nullable MediaType mediaType) {
63+
return this.smartConverter.canWrite(ResolvableType.forType(type), clazz, mediaType);
64+
}
65+
66+
@Override
67+
public void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
68+
throws IOException, HttpMessageNotWritableException {
69+
this.smartConverter.write(t, ResolvableType.forType(type), contentType, outputMessage, null);
70+
}
71+
72+
@Override
73+
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
74+
return this.smartConverter.canRead(ResolvableType.forClass(clazz), mediaType);
75+
}
76+
77+
@Override
78+
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
79+
return this.smartConverter.canWrite(clazz, mediaType);
80+
}
81+
82+
@Override
83+
public List<MediaType> getSupportedMediaTypes() {
84+
return this.smartConverter.getSupportedMediaTypes();
85+
}
86+
87+
@Override
88+
public T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
89+
throws IOException, HttpMessageNotReadableException {
90+
return this.smartConverter.read(clazz, inputMessage);
91+
}
92+
93+
@Override
94+
public void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
95+
throws IOException, HttpMessageNotWritableException {
96+
this.smartConverter.write(t, contentType, outputMessage);
97+
}
98+
99+
}

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/HttpMessageConverters.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.springframework.http.converter.GenericHttpMessageConverter;
2020
import org.springframework.http.converter.HttpMessageConverter;
2121
import org.springframework.http.converter.json.GsonHttpMessageConverter;
22+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
2223
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
2324
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2425
import org.springframework.util.ClassUtils;
@@ -32,6 +33,8 @@
3233
*/
3334
final class HttpMessageConverters {
3435

36+
private static final boolean jacksonPresent;
37+
3538
private static final boolean jackson2Present;
3639

3740
private static final boolean gsonPresent;
@@ -40,6 +43,7 @@ final class HttpMessageConverters {
4043

4144
static {
4245
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
46+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
4347
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
4448
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
4549
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -50,6 +54,9 @@ private HttpMessageConverters() {
5054
}
5155

5256
static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
57+
if (jacksonPresent) {
58+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
59+
}
5360
if (jackson2Present) {
5461
return new MappingJackson2HttpMessageConverter();
5562
}

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public class OAuth2AccessTokenResponseHttpMessageConverter
5252
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
5353
};
5454

55-
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
55+
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters
56+
.getJsonMessageConverter();
5657

5758
private Converter<Map<String, Object>, OAuth2AccessTokenResponse> accessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
5859

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte
5252
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
5353
};
5454

55-
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
55+
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters
56+
.getJsonMessageConverter();
5657

5758
protected Converter<Map<String, String>, OAuth2Error> errorConverter = new OAuth2ErrorConverter();
5859

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/OAuth2ProtectedResourceMetadataFilter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
import org.springframework.http.converter.GenericHttpMessageConverter;
3232
import org.springframework.http.converter.HttpMessageNotWritableException;
3333
import org.springframework.http.converter.json.GsonHttpMessageConverter;
34+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
3435
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
3536
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
3637
import org.springframework.http.server.ServletServerHttpResponse;
38+
import org.springframework.security.oauth2.core.http.converter.GenericHttpMessageConverterAdapter;
3739
import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata;
3840
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
3941
import org.springframework.security.web.util.UrlUtils;
@@ -139,6 +141,8 @@ private static String resolveResourceIdentifier(HttpServletRequest request) {
139141

140142
private static final class HttpMessageConverters {
141143

144+
private static final boolean jacksonPresent;
145+
142146
private static final boolean jackson2Present;
143147

144148
private static final boolean gsonPresent;
@@ -147,6 +151,7 @@ private static final class HttpMessageConverters {
147151

148152
static {
149153
ClassLoader classLoader = HttpMessageConverters.class.getClassLoader();
154+
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader);
150155
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
151156
&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
152157
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
@@ -157,6 +162,9 @@ private HttpMessageConverters() {
157162
}
158163

159164
private static GenericHttpMessageConverter<Object> getJsonMessageConverter() {
165+
if (jacksonPresent) {
166+
return new GenericHttpMessageConverterAdapter<>(new JacksonJsonHttpMessageConverter());
167+
}
160168
if (jackson2Present) {
161169
return new MappingJackson2HttpMessageConverter();
162170
}

0 commit comments

Comments
 (0)