Skip to content

Commit 58bac49

Browse files
committed
JdbcOAuth2AuthorizationService improves support for large data columns
Closes gh-604
1 parent f8fdcd7 commit 58bac49

File tree

2 files changed

+121
-71
lines changed

2 files changed

+121
-71
lines changed

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

Lines changed: 116 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.time.Instant;
2626
import java.util.ArrayList;
2727
import java.util.Collections;
28+
import java.util.HashMap;
2829
import java.util.List;
2930
import java.util.Map;
3031
import java.util.Set;
@@ -72,6 +73,7 @@
7273
* therefore MUST be defined in the database schema.
7374
*
7475
* @author Ovidiu Popa
76+
* @author Joe Grandja
7577
* @since 0.1.2
7678
* @see OAuth2AuthorizationService
7779
* @see OAuth2Authorization
@@ -141,7 +143,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
141143

142144
private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
143145

144-
private static int tokenColumnDataType;
146+
private static Map<String, ColumnMetadata> columnMetadataMap;
145147

146148
private final JdbcOperations jdbcOperations;
147149
private final LobHandler lobHandler;
@@ -177,7 +179,7 @@ public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations,
177179
authorizationRowMapper.setLobHandler(lobHandler);
178180
this.authorizationRowMapper = authorizationRowMapper;
179181
this.authorizationParametersMapper = new OAuth2AuthorizationParametersMapper();
180-
tokenColumnDataType = getColumnDataType(jdbcOperations, "access_token_value", Types.BLOB);
182+
initColumnMetadata(jdbcOperations);
181183
}
182184

183185
@Override
@@ -237,32 +239,26 @@ public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType t
237239
List<SqlParameterValue> parameters = new ArrayList<>();
238240
if (tokenType == null) {
239241
parameters.add(new SqlParameterValue(Types.VARCHAR, token));
240-
parameters.add(mapTokenToSqlParameter(token));
241-
parameters.add(mapTokenToSqlParameter(token));
242-
parameters.add(mapTokenToSqlParameter(token));
242+
parameters.add(mapToSqlParameter("authorization_code_value", token));
243+
parameters.add(mapToSqlParameter("access_token_value", token));
244+
parameters.add(mapToSqlParameter("refresh_token_value", token));
243245
return findBy(UNKNOWN_TOKEN_TYPE_FILTER, parameters);
244246
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
245247
parameters.add(new SqlParameterValue(Types.VARCHAR, token));
246248
return findBy(STATE_FILTER, parameters);
247249
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
248-
parameters.add(mapTokenToSqlParameter(token));
250+
parameters.add(mapToSqlParameter("authorization_code_value", token));
249251
return findBy(AUTHORIZATION_CODE_FILTER, parameters);
250252
} else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
251-
parameters.add(mapTokenToSqlParameter(token));
253+
parameters.add(mapToSqlParameter("access_token_value", token));
252254
return findBy(ACCESS_TOKEN_FILTER, parameters);
253255
} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
254-
parameters.add(mapTokenToSqlParameter(token));
256+
parameters.add(mapToSqlParameter("refresh_token_value", token));
255257
return findBy(REFRESH_TOKEN_FILTER, parameters);
256258
}
257259
return null;
258260
}
259261

260-
private SqlParameterValue mapTokenToSqlParameter(String token) {
261-
return Types.BLOB == tokenColumnDataType ?
262-
new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)) :
263-
new SqlParameterValue(tokenColumnDataType, token);
264-
}
265-
266262
private OAuth2Authorization findBy(String filter, List<SqlParameterValue> parameters) {
267263
try (LobCreator lobCreator = getLobHandler().getLobCreator()) {
268264
PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator,
@@ -348,7 +344,7 @@ public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException
348344
String id = rs.getString("id");
349345
String principalName = rs.getString("principal_name");
350346
String authorizationGrantType = rs.getString("authorization_grant_type");
351-
Map<String, Object> attributes = parseMap(rs.getString("attributes"));
347+
Map<String, Object> attributes = parseMap(getLobValue(rs, "attributes"));
352348

353349
builder.id(id)
354350
.principalName(principalName)
@@ -362,23 +358,23 @@ public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException
362358

363359
Instant tokenIssuedAt;
364360
Instant tokenExpiresAt;
365-
String authorizationCodeValue = getTokenValue(rs, "authorization_code_value");
361+
String authorizationCodeValue = getLobValue(rs, "authorization_code_value");
366362

367363
if (StringUtils.hasText(authorizationCodeValue)) {
368364
tokenIssuedAt = rs.getTimestamp("authorization_code_issued_at").toInstant();
369365
tokenExpiresAt = rs.getTimestamp("authorization_code_expires_at").toInstant();
370-
Map<String, Object> authorizationCodeMetadata = parseMap(rs.getString("authorization_code_metadata"));
366+
Map<String, Object> authorizationCodeMetadata = parseMap(getLobValue(rs, "authorization_code_metadata"));
371367

372368
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
373369
authorizationCodeValue, tokenIssuedAt, tokenExpiresAt);
374370
builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata));
375371
}
376372

377-
String accessTokenValue = getTokenValue(rs, "access_token_value");
373+
String accessTokenValue = getLobValue(rs, "access_token_value");
378374
if (StringUtils.hasText(accessTokenValue)) {
379375
tokenIssuedAt = rs.getTimestamp("access_token_issued_at").toInstant();
380376
tokenExpiresAt = rs.getTimestamp("access_token_expires_at").toInstant();
381-
Map<String, Object> accessTokenMetadata = parseMap(rs.getString("access_token_metadata"));
377+
Map<String, Object> accessTokenMetadata = parseMap(getLobValue(rs, "access_token_metadata"));
382378
OAuth2AccessToken.TokenType tokenType = null;
383379
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
384380
tokenType = OAuth2AccessToken.TokenType.BEARER;
@@ -393,26 +389,26 @@ public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException
393389
builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata));
394390
}
395391

396-
String oidcIdTokenValue = getTokenValue(rs, "oidc_id_token_value");
392+
String oidcIdTokenValue = getLobValue(rs, "oidc_id_token_value");
397393
if (StringUtils.hasText(oidcIdTokenValue)) {
398394
tokenIssuedAt = rs.getTimestamp("oidc_id_token_issued_at").toInstant();
399395
tokenExpiresAt = rs.getTimestamp("oidc_id_token_expires_at").toInstant();
400-
Map<String, Object> oidcTokenMetadata = parseMap(rs.getString("oidc_id_token_metadata"));
396+
Map<String, Object> oidcTokenMetadata = parseMap(getLobValue(rs, "oidc_id_token_metadata"));
401397

402398
OidcIdToken oidcToken = new OidcIdToken(
403399
oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt, (Map<String, Object>) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME));
404400
builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata));
405401
}
406402

407-
String refreshTokenValue = getTokenValue(rs, "refresh_token_value");
403+
String refreshTokenValue = getLobValue(rs, "refresh_token_value");
408404
if (StringUtils.hasText(refreshTokenValue)) {
409405
tokenIssuedAt = rs.getTimestamp("refresh_token_issued_at").toInstant();
410406
tokenExpiresAt = null;
411407
Timestamp refreshTokenExpiresAt = rs.getTimestamp("refresh_token_expires_at");
412408
if (refreshTokenExpiresAt != null) {
413409
tokenExpiresAt = refreshTokenExpiresAt.toInstant();
414410
}
415-
Map<String, Object> refreshTokenMetadata = parseMap(rs.getString("refresh_token_metadata"));
411+
Map<String, Object> refreshTokenMetadata = parseMap(getLobValue(rs, "refresh_token_metadata"));
416412

417413
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
418414
refreshTokenValue, tokenIssuedAt, tokenExpiresAt);
@@ -421,19 +417,20 @@ public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException
421417
return builder.build();
422418
}
423419

424-
private String getTokenValue(ResultSet rs, String tokenColumn) throws SQLException {
425-
String tokenValue = null;
426-
if (Types.BLOB == tokenColumnDataType) {
427-
byte[] tokenValueBytes = this.lobHandler.getBlobAsBytes(rs, tokenColumn);
428-
if (tokenValueBytes != null) {
429-
tokenValue = new String(tokenValueBytes, StandardCharsets.UTF_8);
420+
private String getLobValue(ResultSet rs, String columnName) throws SQLException {
421+
String columnValue = null;
422+
ColumnMetadata columnMetadata = columnMetadataMap.get(columnName);
423+
if (Types.BLOB == columnMetadata.getDataType()) {
424+
byte[] columnValueBytes = this.lobHandler.getBlobAsBytes(rs, columnName);
425+
if (columnValueBytes != null) {
426+
columnValue = new String(columnValueBytes, StandardCharsets.UTF_8);
430427
}
431-
} else if (Types.CLOB == tokenColumnDataType) {
432-
tokenValue = this.lobHandler.getClobAsString(rs, tokenColumn);
428+
} else if (Types.CLOB == columnMetadata.getDataType()) {
429+
columnValue = this.lobHandler.getClobAsString(rs, columnName);
433430
} else {
434-
tokenValue = rs.getString(tokenColumn);
431+
columnValue = rs.getString(columnName);
435432
}
436-
return tokenValue;
433+
return columnValue;
437434
}
438435

439436
public final void setLobHandler(LobHandler lobHandler) {
@@ -491,7 +488,7 @@ public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
491488
parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue()));
492489

493490
String attributes = writeMap(authorization.getAttributes());
494-
parameters.add(new SqlParameterValue(Types.VARCHAR, attributes));
491+
parameters.add(mapToSqlParameter("attributes", attributes));
495492

496493
String state = null;
497494
String authorizationState = authorization.getAttribute(OAuth2ParameterNames.STATE);
@@ -502,12 +499,14 @@ public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
502499

503500
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
504501
authorization.getToken(OAuth2AuthorizationCode.class);
505-
List<SqlParameterValue> authorizationCodeSqlParameters = toSqlParameterList(authorizationCode);
502+
List<SqlParameterValue> authorizationCodeSqlParameters = toSqlParameterList(
503+
"authorization_code_value", "authorization_code_metadata", authorizationCode);
506504
parameters.addAll(authorizationCodeSqlParameters);
507505

508506
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
509507
authorization.getToken(OAuth2AccessToken.class);
510-
List<SqlParameterValue> accessTokenSqlParameters = toSqlParameterList(accessToken);
508+
List<SqlParameterValue> accessTokenSqlParameters = toSqlParameterList(
509+
"access_token_value", "access_token_metadata", accessToken);
511510
parameters.addAll(accessTokenSqlParameters);
512511
String accessTokenType = null;
513512
String accessTokenScopes = null;
@@ -521,11 +520,13 @@ public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
521520
parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes));
522521

523522
OAuth2Authorization.Token<OidcIdToken> oidcIdToken = authorization.getToken(OidcIdToken.class);
524-
List<SqlParameterValue> oidcIdTokenSqlParameters = toSqlParameterList(oidcIdToken);
523+
List<SqlParameterValue> oidcIdTokenSqlParameters = toSqlParameterList(
524+
"oidc_id_token_value", "oidc_id_token_metadata", oidcIdToken);
525525
parameters.addAll(oidcIdTokenSqlParameters);
526526

527527
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
528-
List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(refreshToken);
528+
List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(
529+
"refresh_token_value", "refresh_token_metadata", refreshToken);
529530
parameters.addAll(refreshTokenSqlParameters);
530531
return parameters;
531532
}
@@ -539,7 +540,9 @@ protected final ObjectMapper getObjectMapper() {
539540
return this.objectMapper;
540541
}
541542

542-
private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterList(OAuth2Authorization.Token<T> token) {
543+
private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterList(
544+
String tokenColumnName, String tokenMetadataColumnName, OAuth2Authorization.Token<T> token) {
545+
543546
List<SqlParameterValue> parameters = new ArrayList<>();
544547
String tokenValue = null;
545548
Timestamp tokenIssuedAt = null;
@@ -555,15 +558,11 @@ private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterLi
555558
}
556559
metadata = writeMap(token.getMetadata());
557560
}
558-
if (Types.BLOB == tokenColumnDataType && StringUtils.hasText(tokenValue)) {
559-
byte[] tokenValueBytes = tokenValue.getBytes(StandardCharsets.UTF_8);
560-
parameters.add(new SqlParameterValue(Types.BLOB, tokenValueBytes));
561-
} else {
562-
parameters.add(new SqlParameterValue(tokenColumnDataType, tokenValue));
563-
}
561+
562+
parameters.add(mapToSqlParameter(tokenColumnName, tokenValue));
564563
parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt));
565564
parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt));
566-
parameters.add(new SqlParameterValue(Types.VARCHAR, metadata));
565+
parameters.add(mapToSqlParameter(tokenMetadataColumnName, metadata));
567566
return parameters;
568567
}
569568

@@ -577,26 +576,6 @@ private String writeMap(Map<String, Object> data) {
577576

578577
}
579578

580-
private static Integer getColumnDataType(JdbcOperations jdbcOperations, String columnName, int defaultDataType) {
581-
return jdbcOperations.execute((ConnectionCallback<Integer>) conn -> {
582-
DatabaseMetaData databaseMetaData = conn.getMetaData();
583-
ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName);
584-
if (rs.next()) {
585-
return rs.getInt("DATA_TYPE");
586-
}
587-
// NOTE: (Applies to HSQL)
588-
// When a database object is created with one of the CREATE statements or renamed with the ALTER statement,
589-
// if the name is enclosed in double quotes, the exact name is used as the case-normal form.
590-
// But if it is not enclosed in double quotes,
591-
// the name is converted to uppercase and this uppercase version is stored in the database as the case-normal form.
592-
rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(), columnName.toUpperCase());
593-
if (rs.next()) {
594-
return rs.getInt("DATA_TYPE");
595-
}
596-
return defaultDataType;
597-
});
598-
}
599-
600579
private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
601580
private final LobCreator lobCreator;
602581

@@ -633,4 +612,75 @@ protected void doSetValue(PreparedStatement ps, int parameterPosition, Object ar
633612

634613
}
635614

615+
private static final class ColumnMetadata {
616+
private final String columnName;
617+
private final int dataType;
618+
619+
private ColumnMetadata(String columnName, int dataType) {
620+
this.columnName = columnName;
621+
this.dataType = dataType;
622+
}
623+
624+
private String getColumnName() {
625+
return this.columnName;
626+
}
627+
628+
private int getDataType() {
629+
return this.dataType;
630+
}
631+
632+
}
633+
634+
private static void initColumnMetadata(JdbcOperations jdbcOperations) {
635+
columnMetadataMap = new HashMap<>();
636+
ColumnMetadata columnMetadata;
637+
638+
columnMetadata = getColumnMetadata(jdbcOperations, "attributes", Types.BLOB);
639+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
640+
columnMetadata = getColumnMetadata(jdbcOperations, "authorization_code_value", Types.BLOB);
641+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
642+
columnMetadata = getColumnMetadata(jdbcOperations, "authorization_code_metadata", Types.BLOB);
643+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
644+
columnMetadata = getColumnMetadata(jdbcOperations, "access_token_value", Types.BLOB);
645+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
646+
columnMetadata = getColumnMetadata(jdbcOperations, "access_token_metadata", Types.BLOB);
647+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
648+
columnMetadata = getColumnMetadata(jdbcOperations, "oidc_id_token_value", Types.BLOB);
649+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
650+
columnMetadata = getColumnMetadata(jdbcOperations, "oidc_id_token_metadata", Types.BLOB);
651+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
652+
columnMetadata = getColumnMetadata(jdbcOperations, "refresh_token_value", Types.BLOB);
653+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
654+
columnMetadata = getColumnMetadata(jdbcOperations, "refresh_token_metadata", Types.BLOB);
655+
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
656+
}
657+
658+
private static ColumnMetadata getColumnMetadata(JdbcOperations jdbcOperations, String columnName, int defaultDataType) {
659+
Integer dataType = jdbcOperations.execute((ConnectionCallback<Integer>) conn -> {
660+
DatabaseMetaData databaseMetaData = conn.getMetaData();
661+
ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName);
662+
if (rs.next()) {
663+
return rs.getInt("DATA_TYPE");
664+
}
665+
// NOTE: (Applies to HSQL)
666+
// When a database object is created with one of the CREATE statements or renamed with the ALTER statement,
667+
// if the name is enclosed in double quotes, the exact name is used as the case-normal form.
668+
// But if it is not enclosed in double quotes,
669+
// the name is converted to uppercase and this uppercase version is stored in the database as the case-normal form.
670+
rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(), columnName.toUpperCase());
671+
if (rs.next()) {
672+
return rs.getInt("DATA_TYPE");
673+
}
674+
return null;
675+
});
676+
return new ColumnMetadata(columnName, dataType != null ? dataType : defaultDataType);
677+
}
678+
679+
private static SqlParameterValue mapToSqlParameter(String columnName, String value) {
680+
ColumnMetadata columnMetadata = columnMetadataMap.get(columnName);
681+
return Types.BLOB == columnMetadata.getDataType() && StringUtils.hasText(value) ?
682+
new SqlParameterValue(Types.BLOB, value.getBytes(StandardCharsets.UTF_8)) :
683+
new SqlParameterValue(columnMetadata.getDataType(), value);
684+
}
685+
636686
}

oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,25 @@ CREATE TABLE oauth2_authorization (
88
registered_client_id varchar(100) NOT NULL,
99
principal_name varchar(200) NOT NULL,
1010
authorization_grant_type varchar(100) NOT NULL,
11-
attributes varchar(4000) DEFAULT NULL,
11+
attributes blob DEFAULT NULL,
1212
state varchar(500) DEFAULT NULL,
1313
authorization_code_value blob DEFAULT NULL,
1414
authorization_code_issued_at timestamp DEFAULT NULL,
1515
authorization_code_expires_at timestamp DEFAULT NULL,
16-
authorization_code_metadata varchar(2000) DEFAULT NULL,
16+
authorization_code_metadata blob DEFAULT NULL,
1717
access_token_value blob DEFAULT NULL,
1818
access_token_issued_at timestamp DEFAULT NULL,
1919
access_token_expires_at timestamp DEFAULT NULL,
20-
access_token_metadata varchar(2000) DEFAULT NULL,
20+
access_token_metadata blob DEFAULT NULL,
2121
access_token_type varchar(100) DEFAULT NULL,
2222
access_token_scopes varchar(1000) DEFAULT NULL,
2323
oidc_id_token_value blob DEFAULT NULL,
2424
oidc_id_token_issued_at timestamp DEFAULT NULL,
2525
oidc_id_token_expires_at timestamp DEFAULT NULL,
26-
oidc_id_token_metadata varchar(2000) DEFAULT NULL,
26+
oidc_id_token_metadata blob DEFAULT NULL,
2727
refresh_token_value blob DEFAULT NULL,
2828
refresh_token_issued_at timestamp DEFAULT NULL,
2929
refresh_token_expires_at timestamp DEFAULT NULL,
30-
refresh_token_metadata varchar(2000) DEFAULT NULL,
30+
refresh_token_metadata blob DEFAULT NULL,
3131
PRIMARY KEY (id)
3232
);

0 commit comments

Comments
 (0)