2525import java .time .Instant ;
2626import java .util .ArrayList ;
2727import java .util .Collections ;
28+ import java .util .HashMap ;
2829import java .util .List ;
2930import java .util .Map ;
3031import java .util .Set ;
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}
0 commit comments