Skip to content

Commit 27ae318

Browse files
committed
JdbcRegisteredClientRepository should support Jackson 3
Issue gh-17832 Closes gh-18143
1 parent 7384066 commit 27ae318

File tree

2 files changed

+189
-60
lines changed

2 files changed

+189
-60
lines changed

oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepository.java

Lines changed: 172 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,23 @@
2828
import java.util.Set;
2929
import java.util.function.Function;
3030

31-
import com.fasterxml.jackson.core.type.TypeReference;
31+
import com.fasterxml.jackson.core.JsonProcessingException;
3232
import com.fasterxml.jackson.databind.Module;
3333
import com.fasterxml.jackson.databind.ObjectMapper;
34+
import tools.jackson.databind.JacksonModule;
35+
import tools.jackson.databind.json.JsonMapper;
3436

3537
import org.springframework.aot.hint.RuntimeHints;
3638
import org.springframework.aot.hint.RuntimeHintsRegistrar;
3739
import org.springframework.context.annotation.ImportRuntimeHints;
40+
import org.springframework.core.ParameterizedTypeReference;
3841
import org.springframework.core.io.ClassPathResource;
3942
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
4043
import org.springframework.jdbc.core.JdbcOperations;
4144
import org.springframework.jdbc.core.PreparedStatementSetter;
4245
import org.springframework.jdbc.core.RowMapper;
4346
import org.springframework.jdbc.core.SqlParameterValue;
47+
import org.springframework.security.jackson.SecurityJacksonModules;
4448
import org.springframework.security.jackson2.SecurityJackson2Modules;
4549
import org.springframework.security.oauth2.core.AuthorizationGrantType;
4650
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@@ -134,8 +138,8 @@ public class JdbcRegisteredClientRepository implements RegisteredClientRepositor
134138
public JdbcRegisteredClientRepository(JdbcOperations jdbcOperations) {
135139
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
136140
this.jdbcOperations = jdbcOperations;
137-
this.registeredClientRowMapper = new RegisteredClientRowMapper();
138-
this.registeredClientParametersMapper = new RegisteredClientParametersMapper();
141+
this.registeredClientRowMapper = new JsonMapperRegisteredClientRowMapper();
142+
this.registeredClientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
139143
}
140144

141145
@Override
@@ -206,7 +210,7 @@ private RegisteredClient findBy(String filter, Object... args) {
206210
/**
207211
* Sets the {@link RowMapper} used for mapping the current row in
208212
* {@code java.sql.ResultSet} to {@link RegisteredClient}. The default is
209-
* {@link RegisteredClientRowMapper}.
213+
* {@link JsonMapperRegisteredClientRowMapper}.
210214
* @param registeredClientRowMapper the {@link RowMapper} used for mapping the current
211215
* row in {@code ResultSet} to {@link RegisteredClient}
212216
*/
@@ -218,7 +222,7 @@ public final void setRegisteredClientRowMapper(RowMapper<RegisteredClient> regis
218222
/**
219223
* Sets the {@code Function} used for mapping {@link RegisteredClient} to a
220224
* {@code List} of {@link SqlParameterValue}. The default is
221-
* {@link RegisteredClientParametersMapper}.
225+
* {@link JsonMapperRegisteredClientParametersMapper}.
222226
* @param registeredClientParametersMapper the {@code Function} used for mapping
223227
* {@link RegisteredClient} to a {@code List} of {@link SqlParameterValue}
224228
*/
@@ -242,17 +246,76 @@ protected final Function<RegisteredClient, List<SqlParameterValue>> getRegistere
242246

243247
/**
244248
* The default {@link RowMapper} that maps the current row in
245-
* {@code java.sql.ResultSet} to {@link RegisteredClient}.
249+
* {@code java.sql.ResultSet} to {@link RegisteredClient} using Jackson 3's
250+
* {@link JsonMapper}.
251+
*
252+
* @author Joe Grandja
253+
* @since 7.0
246254
*/
247-
public static class RegisteredClientRowMapper implements RowMapper<RegisteredClient> {
255+
public static class JsonMapperRegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
248256

249-
private ObjectMapper objectMapper = new ObjectMapper();
257+
private final JsonMapper jsonMapper;
250258

251-
public RegisteredClientRowMapper() {
252-
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
253-
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
254-
this.objectMapper.registerModules(securityModules);
255-
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
259+
public JsonMapperRegisteredClientRowMapper() {
260+
this(Jackson3.createJsonMapper());
261+
}
262+
263+
public JsonMapperRegisteredClientRowMapper(JsonMapper jsonMapper) {
264+
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
265+
this.jsonMapper = jsonMapper;
266+
}
267+
268+
@Override
269+
Map<String, Object> readValue(String data) {
270+
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
271+
};
272+
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
273+
.constructType(typeReference.getType());
274+
return this.jsonMapper.readValue(data, javaType);
275+
}
276+
277+
}
278+
279+
/**
280+
* A {@link RowMapper} that maps the current row in {@code java.sql.ResultSet} to
281+
* {@link RegisteredClient} using Jackson 2's {@link ObjectMapper}.
282+
*
283+
* @deprecated Use {@link JsonMapperRegisteredClientRowMapper} to switch to Jackson 3.
284+
*/
285+
@Deprecated(forRemoval = true, since = "7.0")
286+
public static class RegisteredClientRowMapper extends AbstractRegisteredClientRowMapper {
287+
288+
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
289+
290+
public final void setObjectMapper(ObjectMapper objectMapper) {
291+
Assert.notNull(objectMapper, "objectMapper cannot be null");
292+
this.objectMapper = objectMapper;
293+
}
294+
295+
protected final ObjectMapper getObjectMapper() {
296+
return this.objectMapper;
297+
}
298+
299+
@Override
300+
Map<String, Object> readValue(String data) throws JsonProcessingException {
301+
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
302+
};
303+
com.fasterxml.jackson.databind.JavaType javaType = this.objectMapper.getTypeFactory()
304+
.constructType(typeReference.getType());
305+
return this.objectMapper.readValue(data, javaType);
306+
}
307+
308+
}
309+
310+
/**
311+
* The base {@link RowMapper} that maps the current row in {@code java.sql.ResultSet}
312+
* to {@link RegisteredClient}. This is extracted to a distinct class so that
313+
* {@link RegisteredClientRowMapper} can be deprecated in favor of
314+
* {@link JsonMapperRegisteredClientRowMapper}.
315+
*/
316+
private abstract static class AbstractRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
317+
318+
private AbstractRegisteredClientRowMapper() {
256319
}
257320

258321
@Override
@@ -299,25 +362,17 @@ public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException {
299362
return builder.build();
300363
}
301364

302-
public final void setObjectMapper(ObjectMapper objectMapper) {
303-
Assert.notNull(objectMapper, "objectMapper cannot be null");
304-
this.objectMapper = objectMapper;
305-
}
306-
307-
protected final ObjectMapper getObjectMapper() {
308-
return this.objectMapper;
309-
}
310-
311365
private Map<String, Object> parseMap(String data) {
312366
try {
313-
return this.objectMapper.readValue(data, new TypeReference<>() {
314-
});
367+
return readValue(data);
315368
}
316369
catch (Exception ex) {
317370
throw new IllegalArgumentException(ex.getMessage(), ex);
318371
}
319372
}
320373

374+
abstract Map<String, Object> readValue(String data) throws Exception;
375+
321376
private static AuthorizationGrantType resolveAuthorizationGrantType(String authorizationGrantType) {
322377
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals(authorizationGrantType)) {
323378
return AuthorizationGrantType.AUTHORIZATION_CODE;
@@ -350,18 +405,64 @@ else if (ClientAuthenticationMethod.NONE.getValue().equals(clientAuthenticationM
350405

351406
/**
352407
* The default {@code Function} that maps {@link RegisteredClient} to a {@code List}
353-
* of {@link SqlParameterValue}.
408+
* of {@link SqlParameterValue} using an instance of Jackson 3's {@link JsonMapper}.
354409
*/
355-
public static class RegisteredClientParametersMapper
356-
implements Function<RegisteredClient, List<SqlParameterValue>> {
410+
public static class JsonMapperRegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
357411

358-
private ObjectMapper objectMapper = new ObjectMapper();
412+
private final JsonMapper jsonMapper;
359413

360-
public RegisteredClientParametersMapper() {
361-
ClassLoader classLoader = JdbcRegisteredClientRepository.class.getClassLoader();
362-
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
363-
this.objectMapper.registerModules(securityModules);
364-
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
414+
public JsonMapperRegisteredClientParametersMapper() {
415+
this(Jackson3.createJsonMapper());
416+
}
417+
418+
public JsonMapperRegisteredClientParametersMapper(JsonMapper jsonMapper) {
419+
Assert.notNull(jsonMapper, "jsonMapper cannot be null");
420+
this.jsonMapper = jsonMapper;
421+
}
422+
423+
@Override
424+
String writeValueAsString(Map<String, Object> data) throws Exception {
425+
return this.jsonMapper.writeValueAsString(data);
426+
}
427+
428+
}
429+
430+
/**
431+
* A {@code Function} that maps {@link RegisteredClient} to a {@code List} of
432+
* {@link SqlParameterValue} using an instance of Jackson 2's {@link ObjectMapper}.
433+
*
434+
* @deprecated Use {@link JsonMapperRegisteredClientParametersMapper} to switch to
435+
* Jackson 3.
436+
*/
437+
@Deprecated(forRemoval = true, since = "7.0")
438+
public static class RegisteredClientParametersMapper extends AbstractRegisteredClientParametersMapper {
439+
440+
private ObjectMapper objectMapper = Jackson2.createObjectMapper();
441+
442+
public final void setObjectMapper(ObjectMapper objectMapper) {
443+
Assert.notNull(objectMapper, "objectMapper cannot be null");
444+
this.objectMapper = objectMapper;
445+
}
446+
447+
protected final ObjectMapper getObjectMapper() {
448+
return this.objectMapper;
449+
}
450+
451+
@Override
452+
String writeValueAsString(Map<String, Object> data) throws JsonProcessingException {
453+
return this.objectMapper.writeValueAsString(data);
454+
}
455+
456+
}
457+
458+
/**
459+
* The base {@code Function} that maps {@link RegisteredClient} to a {@code List} of
460+
* {@link SqlParameterValue}.
461+
*/
462+
private abstract static class AbstractRegisteredClientParametersMapper
463+
implements Function<RegisteredClient, List<SqlParameterValue>> {
464+
465+
private AbstractRegisteredClientParametersMapper() {
365466
}
366467

367468
@Override
@@ -403,24 +504,52 @@ public List<SqlParameterValue> apply(RegisteredClient registeredClient) {
403504
new SqlParameterValue(Types.VARCHAR, writeMap(registeredClient.getTokenSettings().getSettings())));
404505
}
405506

406-
public final void setObjectMapper(ObjectMapper objectMapper) {
407-
Assert.notNull(objectMapper, "objectMapper cannot be null");
408-
this.objectMapper = objectMapper;
409-
}
410-
411-
protected final ObjectMapper getObjectMapper() {
412-
return this.objectMapper;
413-
}
414-
415507
private String writeMap(Map<String, Object> data) {
416508
try {
417-
return this.objectMapper.writeValueAsString(data);
509+
return writeValueAsString(data);
418510
}
419511
catch (Exception ex) {
420512
throw new IllegalArgumentException(ex.getMessage(), ex);
421513
}
422514
}
423515

516+
abstract String writeValueAsString(Map<String, Object> data) throws Exception;
517+
518+
}
519+
520+
/**
521+
* Nested class to protect from getting {@link NoClassDefFoundError} when Jackson 2 is
522+
* not on the classpath.
523+
*
524+
* @deprecated This is used to allow transition to Jackson 3. Use {@link Jackson3}
525+
* instead.
526+
*/
527+
@Deprecated(forRemoval = true, since = "7.0")
528+
private static final class Jackson2 {
529+
530+
private static ObjectMapper createObjectMapper() {
531+
ObjectMapper objectMapper = new ObjectMapper();
532+
ClassLoader classLoader = Jackson2.class.getClassLoader();
533+
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
534+
objectMapper.registerModules(securityModules);
535+
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
536+
return objectMapper;
537+
}
538+
539+
}
540+
541+
/**
542+
* Nested class used to get a common default instance of {@link JsonMapper}. It is in
543+
* a nested class to protect from getting {@link NoClassDefFoundError} when Jackson 3
544+
* is not on the classpath.
545+
*/
546+
private static final class Jackson3 {
547+
548+
private static JsonMapper createJsonMapper() {
549+
List<JacksonModule> modules = SecurityJacksonModules.getModules(Jackson3.class.getClassLoader());
550+
return JsonMapper.builder().addModules(modules).build();
551+
}
552+
424553
}
425554

426555
static class JdbcRegisteredClientRepositoryRuntimeHintsRegistrar implements RuntimeHintsRegistrar {

oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/JdbcRegisteredClientRepositoryTests.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@
2525
import java.util.Set;
2626
import java.util.function.Function;
2727

28-
import com.fasterxml.jackson.core.type.TypeReference;
29-
import com.fasterxml.jackson.databind.Module;
30-
import com.fasterxml.jackson.databind.ObjectMapper;
3128
import org.junit.jupiter.api.AfterEach;
3229
import org.junit.jupiter.api.BeforeEach;
3330
import org.junit.jupiter.api.Test;
31+
import tools.jackson.databind.JacksonModule;
32+
import tools.jackson.databind.json.JsonMapper;
3433

34+
import org.springframework.core.ParameterizedTypeReference;
3535
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
3636
import org.springframework.jdbc.core.JdbcOperations;
3737
import org.springframework.jdbc.core.JdbcTemplate;
@@ -41,13 +41,12 @@
4141
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
4242
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
4343
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
44-
import org.springframework.security.jackson2.SecurityJackson2Modules;
44+
import org.springframework.security.jackson.SecurityJacksonModules;
4545
import org.springframework.security.oauth2.core.AuthorizationGrantType;
4646
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
4747
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
48-
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
49-
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientRowMapper;
50-
import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
48+
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientParametersMapper;
49+
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.JsonMapperRegisteredClientRowMapper;
5150
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
5251
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
5352
import org.springframework.util.StringUtils;
@@ -222,9 +221,9 @@ public void saveWhenExistingClientSecretThenThrowIllegalArgumentException() {
222221

223222
@Test
224223
public void saveLoadRegisteredClientWhenCustomStrategiesSetThenCalled() throws Exception {
225-
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new RegisteredClientRowMapper());
224+
RowMapper<RegisteredClient> registeredClientRowMapper = spy(new JsonMapperRegisteredClientRowMapper());
226225
this.registeredClientRepository.setRegisteredClientRowMapper(registeredClientRowMapper);
227-
RegisteredClientParametersMapper clientParametersMapper = new RegisteredClientParametersMapper();
226+
JsonMapperRegisteredClientParametersMapper clientParametersMapper = new JsonMapperRegisteredClientParametersMapper();
228227
Function<RegisteredClient, List<SqlParameterValue>> registeredClientParametersMapper = spy(
229228
clientParametersMapper);
230229
this.registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper);
@@ -365,16 +364,14 @@ private RegisteredClient findBy(String filter, Object... args) {
365364
return !result.isEmpty() ? result.get(0) : null;
366365
}
367366

368-
@SuppressWarnings("removal")
369367
private static final class CustomRegisteredClientRowMapper implements RowMapper<RegisteredClient> {
370368

371-
private final ObjectMapper objectMapper = new ObjectMapper();
369+
private final JsonMapper jsonMapper;
372370

373371
private CustomRegisteredClientRowMapper() {
374-
ClassLoader classLoader = CustomJdbcRegisteredClientRepository.class.getClassLoader();
375-
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
376-
this.objectMapper.registerModules(securityModules);
377-
this.objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
372+
List<JacksonModule> modules = SecurityJacksonModules
373+
.getModules(CustomRegisteredClientRowMapper.class.getClassLoader());
374+
this.jsonMapper = JsonMapper.builder().addModules(modules).build();
378375
}
379376

380377
@Override
@@ -418,9 +415,12 @@ public RegisteredClient mapRow(ResultSet rs, int rowNum) throws SQLException {
418415
}
419416

420417
private Map<String, Object> parseMap(String data) {
418+
final ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<>() {
419+
};
421420
try {
422-
return this.objectMapper.readValue(data, new TypeReference<>() {
423-
});
421+
tools.jackson.databind.JavaType javaType = this.jsonMapper.getTypeFactory()
422+
.constructType(typeReference.getType());
423+
return this.jsonMapper.readValue(data, javaType);
424424
}
425425
catch (Exception ex) {
426426
throw new IllegalArgumentException(ex.getMessage(), ex);

0 commit comments

Comments
 (0)