JdbcOAuth2AuthorizationService improves support for large data columns

Closes gh-604
This commit is contained in:
Joe Grandja
2022-01-25 16:02:14 -05:00
parent f8fdcd7ae9
commit 58bac49f97
2 changed files with 121 additions and 71 deletions

View File

@@ -25,6 +25,7 @@ import java.sql.Types;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -72,6 +73,7 @@ import org.springframework.util.StringUtils;
* therefore MUST be defined in the database schema.
*
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.2
* @see OAuth2AuthorizationService
* @see OAuth2Authorization
@@ -141,7 +143,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
private static int tokenColumnDataType;
private static Map<String, ColumnMetadata> columnMetadataMap;
private final JdbcOperations jdbcOperations;
private final LobHandler lobHandler;
@@ -177,7 +179,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
authorizationRowMapper.setLobHandler(lobHandler);
this.authorizationRowMapper = authorizationRowMapper;
this.authorizationParametersMapper = new OAuth2AuthorizationParametersMapper();
tokenColumnDataType = getColumnDataType(jdbcOperations, "access_token_value", Types.BLOB);
initColumnMetadata(jdbcOperations);
}
@Override
@@ -237,32 +239,26 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
List<SqlParameterValue> parameters = new ArrayList<>();
if (tokenType == null) {
parameters.add(new SqlParameterValue(Types.VARCHAR, token));
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapToSqlParameter("authorization_code_value", token));
parameters.add(mapToSqlParameter("access_token_value", token));
parameters.add(mapToSqlParameter("refresh_token_value", token));
return findBy(UNKNOWN_TOKEN_TYPE_FILTER, parameters);
} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
parameters.add(new SqlParameterValue(Types.VARCHAR, token));
return findBy(STATE_FILTER, parameters);
} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapToSqlParameter("authorization_code_value", token));
return findBy(AUTHORIZATION_CODE_FILTER, parameters);
} else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapToSqlParameter("access_token_value", token));
return findBy(ACCESS_TOKEN_FILTER, parameters);
} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
parameters.add(mapTokenToSqlParameter(token));
parameters.add(mapToSqlParameter("refresh_token_value", token));
return findBy(REFRESH_TOKEN_FILTER, parameters);
}
return null;
}
private SqlParameterValue mapTokenToSqlParameter(String token) {
return Types.BLOB == tokenColumnDataType ?
new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)) :
new SqlParameterValue(tokenColumnDataType, token);
}
private OAuth2Authorization findBy(String filter, List<SqlParameterValue> parameters) {
try (LobCreator lobCreator = getLobHandler().getLobCreator()) {
PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator,
@@ -348,7 +344,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
String id = rs.getString("id");
String principalName = rs.getString("principal_name");
String authorizationGrantType = rs.getString("authorization_grant_type");
Map<String, Object> attributes = parseMap(rs.getString("attributes"));
Map<String, Object> attributes = parseMap(getLobValue(rs, "attributes"));
builder.id(id)
.principalName(principalName)
@@ -362,23 +358,23 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
Instant tokenIssuedAt;
Instant tokenExpiresAt;
String authorizationCodeValue = getTokenValue(rs, "authorization_code_value");
String authorizationCodeValue = getLobValue(rs, "authorization_code_value");
if (StringUtils.hasText(authorizationCodeValue)) {
tokenIssuedAt = rs.getTimestamp("authorization_code_issued_at").toInstant();
tokenExpiresAt = rs.getTimestamp("authorization_code_expires_at").toInstant();
Map<String, Object> authorizationCodeMetadata = parseMap(rs.getString("authorization_code_metadata"));
Map<String, Object> authorizationCodeMetadata = parseMap(getLobValue(rs, "authorization_code_metadata"));
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
authorizationCodeValue, tokenIssuedAt, tokenExpiresAt);
builder.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata));
}
String accessTokenValue = getTokenValue(rs, "access_token_value");
String accessTokenValue = getLobValue(rs, "access_token_value");
if (StringUtils.hasText(accessTokenValue)) {
tokenIssuedAt = rs.getTimestamp("access_token_issued_at").toInstant();
tokenExpiresAt = rs.getTimestamp("access_token_expires_at").toInstant();
Map<String, Object> accessTokenMetadata = parseMap(rs.getString("access_token_metadata"));
Map<String, Object> accessTokenMetadata = parseMap(getLobValue(rs, "access_token_metadata"));
OAuth2AccessToken.TokenType tokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
tokenType = OAuth2AccessToken.TokenType.BEARER;
@@ -393,18 +389,18 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
builder.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata));
}
String oidcIdTokenValue = getTokenValue(rs, "oidc_id_token_value");
String oidcIdTokenValue = getLobValue(rs, "oidc_id_token_value");
if (StringUtils.hasText(oidcIdTokenValue)) {
tokenIssuedAt = rs.getTimestamp("oidc_id_token_issued_at").toInstant();
tokenExpiresAt = rs.getTimestamp("oidc_id_token_expires_at").toInstant();
Map<String, Object> oidcTokenMetadata = parseMap(rs.getString("oidc_id_token_metadata"));
Map<String, Object> oidcTokenMetadata = parseMap(getLobValue(rs, "oidc_id_token_metadata"));
OidcIdToken oidcToken = new OidcIdToken(
oidcIdTokenValue, tokenIssuedAt, tokenExpiresAt, (Map<String, Object>) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME));
builder.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata));
}
String refreshTokenValue = getTokenValue(rs, "refresh_token_value");
String refreshTokenValue = getLobValue(rs, "refresh_token_value");
if (StringUtils.hasText(refreshTokenValue)) {
tokenIssuedAt = rs.getTimestamp("refresh_token_issued_at").toInstant();
tokenExpiresAt = null;
@@ -412,7 +408,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
if (refreshTokenExpiresAt != null) {
tokenExpiresAt = refreshTokenExpiresAt.toInstant();
}
Map<String, Object> refreshTokenMetadata = parseMap(rs.getString("refresh_token_metadata"));
Map<String, Object> refreshTokenMetadata = parseMap(getLobValue(rs, "refresh_token_metadata"));
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
refreshTokenValue, tokenIssuedAt, tokenExpiresAt);
@@ -421,19 +417,20 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return builder.build();
}
private String getTokenValue(ResultSet rs, String tokenColumn) throws SQLException {
String tokenValue = null;
if (Types.BLOB == tokenColumnDataType) {
byte[] tokenValueBytes = this.lobHandler.getBlobAsBytes(rs, tokenColumn);
if (tokenValueBytes != null) {
tokenValue = new String(tokenValueBytes, StandardCharsets.UTF_8);
private String getLobValue(ResultSet rs, String columnName) throws SQLException {
String columnValue = null;
ColumnMetadata columnMetadata = columnMetadataMap.get(columnName);
if (Types.BLOB == columnMetadata.getDataType()) {
byte[] columnValueBytes = this.lobHandler.getBlobAsBytes(rs, columnName);
if (columnValueBytes != null) {
columnValue = new String(columnValueBytes, StandardCharsets.UTF_8);
}
} else if (Types.CLOB == tokenColumnDataType) {
tokenValue = this.lobHandler.getClobAsString(rs, tokenColumn);
} else if (Types.CLOB == columnMetadata.getDataType()) {
columnValue = this.lobHandler.getClobAsString(rs, columnName);
} else {
tokenValue = rs.getString(tokenColumn);
columnValue = rs.getString(columnName);
}
return tokenValue;
return columnValue;
}
public final void setLobHandler(LobHandler lobHandler) {
@@ -491,7 +488,7 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue()));
String attributes = writeMap(authorization.getAttributes());
parameters.add(new SqlParameterValue(Types.VARCHAR, attributes));
parameters.add(mapToSqlParameter("attributes", attributes));
String state = null;
String authorizationState = authorization.getAttribute(OAuth2ParameterNames.STATE);
@@ -502,12 +499,14 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
authorization.getToken(OAuth2AuthorizationCode.class);
List<SqlParameterValue> authorizationCodeSqlParameters = toSqlParameterList(authorizationCode);
List<SqlParameterValue> authorizationCodeSqlParameters = toSqlParameterList(
"authorization_code_value", "authorization_code_metadata", authorizationCode);
parameters.addAll(authorizationCodeSqlParameters);
OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
authorization.getToken(OAuth2AccessToken.class);
List<SqlParameterValue> accessTokenSqlParameters = toSqlParameterList(accessToken);
List<SqlParameterValue> accessTokenSqlParameters = toSqlParameterList(
"access_token_value", "access_token_metadata", accessToken);
parameters.addAll(accessTokenSqlParameters);
String accessTokenType = null;
String accessTokenScopes = null;
@@ -521,11 +520,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes));
OAuth2Authorization.Token<OidcIdToken> oidcIdToken = authorization.getToken(OidcIdToken.class);
List<SqlParameterValue> oidcIdTokenSqlParameters = toSqlParameterList(oidcIdToken);
List<SqlParameterValue> oidcIdTokenSqlParameters = toSqlParameterList(
"oidc_id_token_value", "oidc_id_token_metadata", oidcIdToken);
parameters.addAll(oidcIdTokenSqlParameters);
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(refreshToken);
List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(
"refresh_token_value", "refresh_token_metadata", refreshToken);
parameters.addAll(refreshTokenSqlParameters);
return parameters;
}
@@ -539,7 +540,9 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
return this.objectMapper;
}
private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterList(OAuth2Authorization.Token<T> token) {
private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterList(
String tokenColumnName, String tokenMetadataColumnName, OAuth2Authorization.Token<T> token) {
List<SqlParameterValue> parameters = new ArrayList<>();
String tokenValue = null;
Timestamp tokenIssuedAt = null;
@@ -555,15 +558,11 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
metadata = writeMap(token.getMetadata());
}
if (Types.BLOB == tokenColumnDataType && StringUtils.hasText(tokenValue)) {
byte[] tokenValueBytes = tokenValue.getBytes(StandardCharsets.UTF_8);
parameters.add(new SqlParameterValue(Types.BLOB, tokenValueBytes));
} else {
parameters.add(new SqlParameterValue(tokenColumnDataType, tokenValue));
}
parameters.add(mapToSqlParameter(tokenColumnName, tokenValue));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt));
parameters.add(new SqlParameterValue(Types.VARCHAR, metadata));
parameters.add(mapToSqlParameter(tokenMetadataColumnName, metadata));
return parameters;
}
@@ -577,26 +576,6 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
private static Integer getColumnDataType(JdbcOperations jdbcOperations, String columnName, int defaultDataType) {
return jdbcOperations.execute((ConnectionCallback<Integer>) conn -> {
DatabaseMetaData databaseMetaData = conn.getMetaData();
ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName);
if (rs.next()) {
return rs.getInt("DATA_TYPE");
}
// NOTE: (Applies to HSQL)
// When a database object is created with one of the CREATE statements or renamed with the ALTER statement,
// if the name is enclosed in double quotes, the exact name is used as the case-normal form.
// But if it is not enclosed in double quotes,
// the name is converted to uppercase and this uppercase version is stored in the database as the case-normal form.
rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(), columnName.toUpperCase());
if (rs.next()) {
return rs.getInt("DATA_TYPE");
}
return defaultDataType;
});
}
private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
private final LobCreator lobCreator;
@@ -633,4 +612,75 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic
}
private static final class ColumnMetadata {
private final String columnName;
private final int dataType;
private ColumnMetadata(String columnName, int dataType) {
this.columnName = columnName;
this.dataType = dataType;
}
private String getColumnName() {
return this.columnName;
}
private int getDataType() {
return this.dataType;
}
}
private static void initColumnMetadata(JdbcOperations jdbcOperations) {
columnMetadataMap = new HashMap<>();
ColumnMetadata columnMetadata;
columnMetadata = getColumnMetadata(jdbcOperations, "attributes", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "authorization_code_value", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "authorization_code_metadata", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "access_token_value", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "access_token_metadata", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "oidc_id_token_value", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "oidc_id_token_metadata", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "refresh_token_value", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
columnMetadata = getColumnMetadata(jdbcOperations, "refresh_token_metadata", Types.BLOB);
columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
}
private static ColumnMetadata getColumnMetadata(JdbcOperations jdbcOperations, String columnName, int defaultDataType) {
Integer dataType = jdbcOperations.execute((ConnectionCallback<Integer>) conn -> {
DatabaseMetaData databaseMetaData = conn.getMetaData();
ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName);
if (rs.next()) {
return rs.getInt("DATA_TYPE");
}
// NOTE: (Applies to HSQL)
// When a database object is created with one of the CREATE statements or renamed with the ALTER statement,
// if the name is enclosed in double quotes, the exact name is used as the case-normal form.
// But if it is not enclosed in double quotes,
// the name is converted to uppercase and this uppercase version is stored in the database as the case-normal form.
rs = databaseMetaData.getColumns(null, null, TABLE_NAME.toUpperCase(), columnName.toUpperCase());
if (rs.next()) {
return rs.getInt("DATA_TYPE");
}
return null;
});
return new ColumnMetadata(columnName, dataType != null ? dataType : defaultDataType);
}
private static SqlParameterValue mapToSqlParameter(String columnName, String value) {
ColumnMetadata columnMetadata = columnMetadataMap.get(columnName);
return Types.BLOB == columnMetadata.getDataType() && StringUtils.hasText(value) ?
new SqlParameterValue(Types.BLOB, value.getBytes(StandardCharsets.UTF_8)) :
new SqlParameterValue(columnMetadata.getDataType(), value);
}
}

View File

@@ -8,25 +8,25 @@ CREATE TABLE oauth2_authorization (
registered_client_id varchar(100) NOT NULL,
principal_name varchar(200) NOT NULL,
authorization_grant_type varchar(100) NOT NULL,
attributes varchar(4000) DEFAULT NULL,
attributes blob DEFAULT NULL,
state varchar(500) DEFAULT NULL,
authorization_code_value blob DEFAULT NULL,
authorization_code_issued_at timestamp DEFAULT NULL,
authorization_code_expires_at timestamp DEFAULT NULL,
authorization_code_metadata varchar(2000) DEFAULT NULL,
authorization_code_metadata blob DEFAULT NULL,
access_token_value blob DEFAULT NULL,
access_token_issued_at timestamp DEFAULT NULL,
access_token_expires_at timestamp DEFAULT NULL,
access_token_metadata varchar(2000) DEFAULT NULL,
access_token_metadata blob DEFAULT NULL,
access_token_type varchar(100) DEFAULT NULL,
access_token_scopes varchar(1000) DEFAULT NULL,
oidc_id_token_value blob DEFAULT NULL,
oidc_id_token_issued_at timestamp DEFAULT NULL,
oidc_id_token_expires_at timestamp DEFAULT NULL,
oidc_id_token_metadata varchar(2000) DEFAULT NULL,
oidc_id_token_metadata blob DEFAULT NULL,
refresh_token_value blob DEFAULT NULL,
refresh_token_issued_at timestamp DEFAULT NULL,
refresh_token_expires_at timestamp DEFAULT NULL,
refresh_token_metadata varchar(2000) DEFAULT NULL,
refresh_token_metadata blob DEFAULT NULL,
PRIMARY KEY (id)
);