diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java index 810bb2b6..1d57bc0d 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationService.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,9 +141,10 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER; + private static int tokenColumnDataType; + private final JdbcOperations jdbcOperations; private final LobHandler lobHandler; - private static int tokenColumnType; private RowMapper authorizationRowMapper; private Function> authorizationParametersMapper; @@ -172,15 +173,13 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic Assert.notNull(lobHandler, "lobHandler cannot be null"); this.jdbcOperations = jdbcOperations; this.lobHandler = lobHandler; - tokenColumnType = getColumnDataType(jdbcOperations, "access_token_value"); OAuth2AuthorizationRowMapper authorizationRowMapper = new OAuth2AuthorizationRowMapper(registeredClientRepository); authorizationRowMapper.setLobHandler(lobHandler); this.authorizationRowMapper = authorizationRowMapper; - OAuth2AuthorizationParametersMapper authorizationParametersMapper = new OAuth2AuthorizationParametersMapper(); - this.authorizationParametersMapper = authorizationParametersMapper; + this.authorizationParametersMapper = new OAuth2AuthorizationParametersMapper(); + tokenColumnDataType = getColumnDataType(jdbcOperations, "access_token_value", Types.BLOB); } - @Override public void save(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); @@ -259,10 +258,9 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } private SqlParameterValue mapTokenToSqlParameter(String token) { - if (Types.BLOB == tokenColumnType) { - return new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)); - } - return new SqlParameterValue(tokenColumnType, token); + return Types.BLOB == tokenColumnDataType ? + new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)) : + new SqlParameterValue(tokenColumnDataType, token); } private OAuth2Authorization findBy(String filter, List parameters) { @@ -425,18 +423,16 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic private String getTokenValue(ResultSet rs, String tokenColumn) throws SQLException { String tokenValue = null; - if (Types.CLOB == tokenColumnType) { + if (Types.BLOB == tokenColumnDataType) { + byte[] tokenValueBytes = this.lobHandler.getBlobAsBytes(rs, tokenColumn); + if (tokenValueBytes != null) { + tokenValue = new String(tokenValueBytes, StandardCharsets.UTF_8); + } + } else if (Types.CLOB == tokenColumnDataType) { tokenValue = this.lobHandler.getClobAsString(rs, tokenColumn); - } - if (Types.VARCHAR == tokenColumnType) { + } else { tokenValue = rs.getString(tokenColumn); } - if (Types.BLOB == tokenColumnType) { - byte[] tokenValueByte = this.lobHandler.getBlobAsBytes(rs, tokenColumn); - if (tokenValueByte != null) { - tokenValue = new String(tokenValueByte, StandardCharsets.UTF_8); - } - } return tokenValue; } @@ -559,13 +555,12 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } metadata = writeMap(token.getMetadata()); } - if (Types.BLOB == tokenColumnType && StringUtils.hasText(tokenValue)) { - byte[] tokenValueAsBytes = tokenValue.getBytes(StandardCharsets.UTF_8); - parameters.add(new SqlParameterValue(tokenColumnType, tokenValueAsBytes)); + 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(tokenColumnType, tokenValue)); + parameters.add(new SqlParameterValue(tokenColumnDataType, tokenValue)); } - parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt)); parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt)); parameters.add(new SqlParameterValue(Types.VARCHAR, metadata)); @@ -582,22 +577,25 @@ public class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationServic } - private static int getColumnDataType(JdbcOperations jdbcOperations, String columnName){ - return jdbcOperations.execute((ConnectionCallback) con -> { - DatabaseMetaData databaseMetaData = con.getMetaData(); - ResultSet rs = databaseMetaData.getColumns(null, null, TABLE_NAME, columnName); - if (rs.next()) { - return rs.getInt("DATA_TYPE"); - } - // NOTE: When using HSQL: When a database object is created with one of the CREATE statements 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 Types.NULL; - }); - } + private static Integer getColumnDataType(JdbcOperations jdbcOperations, String columnName, int defaultDataType) { + return jdbcOperations.execute((ConnectionCallback) 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; diff --git a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema-postgres.sql b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema-postgres.sql deleted file mode 100644 index f9cc0f7e..00000000 --- a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema-postgres.sql +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -CREATE TABLE oauth2_authorization ( - id varchar(100) NOT NULL, - registered_client_id varchar(100) NOT NULL, - principal_name varchar(200) NOT NULL, - authorization_grant_type varchar(100) NOT NULL, - attributes varchar(15000) DEFAULT NULL, - state varchar(500) DEFAULT NULL, - authorization_code_value text DEFAULT NULL, - authorization_code_issued_at timestamp DEFAULT NULL, - authorization_code_expires_at timestamp DEFAULT NULL, - authorization_code_metadata varchar(2000) DEFAULT NULL, - access_token_value text 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_type varchar(100) DEFAULT NULL, - access_token_scopes varchar(1000) DEFAULT NULL, - oidc_id_token_value text 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, - refresh_token_value text DEFAULT NULL, - refresh_token_issued_at timestamp DEFAULT NULL, - refresh_token_expires_at timestamp DEFAULT NULL, - refresh_token_metadata varchar(2000) DEFAULT NULL, - PRIMARY KEY (id) -); diff --git a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql index 96990663..bbfbe24f 100644 --- a/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql +++ b/oauth2-authorization-server/src/main/resources/org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql @@ -1,3 +1,8 @@ +/* +IMPORTANT: + If using PostgreSQL, update ALL columns defined with 'blob' to 'text', + as PostgreSQL does not support the 'blob' data type. +*/ CREATE TABLE oauth2_authorization ( id varchar(100) NOT NULL, registered_client_id varchar(100) NOT NULL, diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java index 1e3a4874..85941ed4 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JdbcOAuth2AuthorizationServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,6 @@ import org.springframework.jdbc.core.SqlParameterValue; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -76,7 +75,7 @@ import static org.mockito.Mockito.when; public class JdbcOAuth2AuthorizationServiceTests { private static final String OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql"; private static final String CUSTOM_OAUTH2_AUTHORIZATION_SCHEMA_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema.sql"; - private static final String OAUTH2_AUTHORIZATION_SCHEMA_CLOB_COLUMN_TYPE_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql"; + private static final String OAUTH2_AUTHORIZATION_SCHEMA_CLOB_DATA_TYPE_SQL_RESOURCE = "org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql"; private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); private static final String ID = "id"; @@ -417,12 +416,13 @@ public class JdbcOAuth2AuthorizationServiceTests { } @Test - public void tableDefinitionWhenClobSqlTypeThenUpdateAuthorization() { - EmbeddedDatabase db = createDb(OAUTH2_AUTHORIZATION_SCHEMA_CLOB_COLUMN_TYPE_SQL_RESOURCE); - OAuth2AuthorizationService authorizationService = - new JdbcOAuth2AuthorizationService(new JdbcTemplate(db), this.registeredClientRepository); + public void tableDefinitionWhenClobSqlTypeThenAuthorizationUpdated() { when(this.registeredClientRepository.findById(eq(REGISTERED_CLIENT.getId()))) .thenReturn(REGISTERED_CLIENT); + + EmbeddedDatabase db = createDb(OAUTH2_AUTHORIZATION_SCHEMA_CLOB_DATA_TYPE_SQL_RESOURCE); + OAuth2AuthorizationService authorizationService = + new JdbcOAuth2AuthorizationService(new JdbcTemplate(db), this.registeredClientRepository); OAuth2Authorization originalAuthorization = OAuth2Authorization.withRegisteredClient(REGISTERED_CLIENT) .id(ID) .principalName(PRINCIPAL_NAME) @@ -512,14 +512,11 @@ public class JdbcOAuth2AuthorizationServiceTests { private CustomJdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations, RegisteredClientRepository registeredClientRepository) { - super(jdbcOperations, registeredClientRepository, new DefaultLobHandler()); + super(jdbcOperations, registeredClientRepository); setAuthorizationRowMapper(new CustomOAuth2AuthorizationRowMapper(registeredClientRepository)); setAuthorizationParametersMapper(new CustomOAuth2AuthorizationParametersMapper()); - } - - @Override public void save(OAuth2Authorization authorization) { List parameters = getAuthorizationParametersMapper().apply(authorization); diff --git a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql index f3fb8a53..22ab5d89 100644 --- a/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql +++ b/oauth2-authorization-server/src/test/resources/org/springframework/security/oauth2/server/authorization/custom-oauth2-authorization-schema-clob-data-type.sql @@ -1,24 +1,9 @@ -/* - * Copyright 2020-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ CREATE TABLE oauth2_authorization ( id varchar(100) NOT NULL, registered_client_id varchar(100) NOT NULL, principal_name varchar(200) NOT NULL, authorization_grant_type varchar(100) NOT NULL, - attributes varchar(15000) DEFAULT NULL, + attributes varchar(4000) DEFAULT NULL, state varchar(500) DEFAULT NULL, authorization_code_value clob DEFAULT NULL, authorization_code_issued_at timestamp DEFAULT NULL,