diff --git a/core/src/main/java/org/springframework/security/core/userdetails/jdbc/JdbcDaoImpl.java b/core/src/main/java/org/springframework/security/core/userdetails/jdbc/JdbcDaoImpl.java index 47f691ca6a6..48ca29b0556 100644 --- a/core/src/main/java/org/springframework/security/core/userdetails/jdbc/JdbcDaoImpl.java +++ b/core/src/main/java/org/springframework/security/core/userdetails/jdbc/JdbcDaoImpl.java @@ -21,13 +21,19 @@ import java.util.List; import java.util.Set; -import org.springframework.context.ApplicationContextException; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.AuthorityUtils; @@ -108,8 +114,9 @@ * @author Ben Alex * @author colin sampaleanu * @author Luke Taylor + * @author Yanming Zhou */ -public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService, MessageSourceAware { +public class JdbcDaoImpl implements UserDetailsService, MessageSourceAware, InitializingBean { public static final String DEFAULT_USER_SCHEMA_DDL_LOCATION = "org/springframework/security/core/userdetails/jdbc/users.ddl"; @@ -131,6 +138,10 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService, M + "where gm.username = ? " + "and g.id = ga.group_id " + "and g.id = gm.group_id"; // @formatter:on + protected final Log logger = LogFactory.getLog(getClass()); + + private @Nullable JdbcTemplate jdbcTemplate; + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private String authoritiesByUsernameQuery; @@ -153,6 +164,60 @@ public JdbcDaoImpl() { this.groupAuthoritiesByUsernameQuery = DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY; } + /** + * Set the JDBC DataSource to be used by this DAO. + */ + public final void setDataSource(DataSource dataSource) { + if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + } + + public final @Nullable DataSource getDataSource() { + return (this.jdbcTemplate != null) ? this.jdbcTemplate.getDataSource() : null; + } + + /** + * Set the JdbcTemplate for this DAO explicitly, as an alternative to specifying a + * DataSource. + */ + public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Return the JdbcTemplate for this DAO, pre-initialized with the DataSource or set + * explicitly. + */ + public final @Nullable JdbcTemplate getJdbcTemplate() { + return this.jdbcTemplate; + } + + @Override + public final void afterPropertiesSet() { + // Let abstract subclasses check their configuration. + checkDaoConfig(); + + // Let concrete implementations initialize themselves. + try { + initDao(); + } + catch (Exception ex) { + throw new BeanInitializationException("Initialization of DAO failed", ex); + } + } + + protected void checkDaoConfig() { + if (this.jdbcTemplate == null) { + throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required"); + } + } + + protected void initDao() { + Assert.isTrue(this.enableAuthorities || this.enableGroups, + "Use of either authorities or groups must be enabled"); + } + /** * @return the messages */ @@ -174,12 +239,6 @@ public String getUsersByUsernameQuery() { return this.usersByUsernameQuery; } - @Override - protected void initDao() throws ApplicationContextException { - Assert.isTrue(this.enableAuthorities || this.enableGroups, - "Use of either authorities or groups must be enabled"); - } - @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List users = loadUsersByUsername(username); @@ -219,7 +278,7 @@ protected List loadUsersByUsername(String username) { return new User(username1, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES); }; // @formatter:on - return getJdbc().query(this.usersByUsernameQuery, mapper, username); + return requireJdbcTemplate().query(this.usersByUsernameQuery, mapper, username); } /** @@ -227,7 +286,7 @@ protected List loadUsersByUsername(String username) { * @return a list of GrantedAuthority objects for the user */ protected List loadUserAuthorities(String username) { - return getJdbc().query(this.authoritiesByUsernameQuery, (rs, rowNum) -> { + return requireJdbcTemplate().query(this.authoritiesByUsernameQuery, (rs, rowNum) -> { String roleName = JdbcDaoImpl.this.rolePrefix + rs.getString(2); return new SimpleGrantedAuthority(roleName); }, username); @@ -239,7 +298,7 @@ protected List loadUserAuthorities(String username) { * @return a list of GrantedAuthority objects for the user */ protected List loadGroupAuthorities(String username) { - return getJdbc().query(this.groupAuthoritiesByUsernameQuery, (rs, rowNum) -> { + return requireJdbcTemplate().query(this.groupAuthoritiesByUsernameQuery, (rs, rowNum) -> { String roleName = getRolePrefix() + rs.getString(3); return new SimpleGrantedAuthority(roleName); }, username); @@ -375,8 +434,8 @@ public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } - private JdbcTemplate getJdbc() { - JdbcTemplate template = getJdbcTemplate(); + protected JdbcTemplate requireJdbcTemplate() { + JdbcTemplate template = this.jdbcTemplate; Assert.notNull(template, "JdbcTemplate cannot be null"); return template; } diff --git a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java index 2c38fd74c2b..353e99b5c02 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -27,11 +27,9 @@ import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; -import org.springframework.context.ApplicationContextException; import org.springframework.core.log.LogMessage; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; import org.springframework.security.access.AccessDeniedException; @@ -67,6 +65,7 @@ * * @author Luke Taylor * @author Junhyeok Lee + * @author Yanming Zhou * @since 2.0 */ public class JdbcUserDetailsManager extends JdbcDaoImpl @@ -209,7 +208,7 @@ public void setGrantedAuthorityMapper(RowMapper mapper) { } @Override - protected void initDao() throws ApplicationContextException { + protected void initDao() { if (this.authenticationManager == null) { this.logger.info( "No authentication manager set. Reauthentication of users when changing passwords will not be performed."); @@ -471,12 +470,6 @@ private int findGroupId(String group) { return groupId; } - private JdbcTemplate requireJdbcTemplate() { - JdbcTemplate jdbc = getJdbcTemplate(); - Assert.notNull(jdbc, "JdbcTemplate cannot be null"); - return jdbc; - } - /** * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. diff --git a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java index bf0590bcd43..d7c545b3a7b 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -62,6 +62,7 @@ * @author Luke Taylor * @author dae won * @author Junhyeok Lee + * @author Yanming Zhou */ public class JdbcUserDetailsManagerTests { @@ -104,7 +105,7 @@ public void initializeManagerAndCreateTables() { this.manager.setDeleteUserAuthoritiesSql(JdbcUserDetailsManager.DEF_DELETE_USER_AUTHORITIES_SQL); this.manager.setDeleteUserSql(JdbcUserDetailsManager.DEF_DELETE_USER_SQL); this.manager.setChangePasswordSql(JdbcUserDetailsManager.DEF_CHANGE_PASSWORD_SQL); - this.manager.initDao(); + this.manager.afterPropertiesSet(); this.template = this.manager.getJdbcTemplate(); this.template.execute("create table users(username varchar(20) not null primary key," + "password varchar(20) not null, enabled boolean not null)"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java index 055af5f8cd2..08be68a7eda 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImpl.java @@ -20,22 +20,28 @@ import java.sql.SQLException; import java.util.Date; +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.core.log.LogMessage; import org.springframework.dao.DataAccessException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.support.JdbcDaoSupport; /** * JDBC based persistent login token repository implementation. * * @author Luke Taylor + * @author Yanming Zhou * @since 2.0 */ -public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository { +public class JdbcTokenRepositoryImpl implements PersistentTokenRepository, InitializingBean { /** Default SQL for creating the database table to store the tokens */ public static final String CREATE_TABLE_SQL = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, " @@ -53,6 +59,10 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten /** The default SQL used by removeUserTokens */ public static final String DEF_REMOVE_USER_TOKENS_SQL = "delete from persistent_logins where username = ?"; + protected final Log logger = LogFactory.getLog(getClass()); + + private @Nullable JdbcTemplate jdbcTemplate; + private String tokensBySeriesSql = DEF_TOKEN_BY_SERIES_SQL; private String insertTokenSql = DEF_INSERT_TOKEN_SQL; @@ -64,6 +74,54 @@ public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements Persisten private boolean createTableOnStartup; @Override + public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { + // Let abstract subclasses check their configuration. + checkDaoConfig(); + + // Let concrete implementations initialize themselves. + try { + initDao(); + } + catch (Exception ex) { + throw new BeanInitializationException("Initialization of DAO failed", ex); + } + } + + /** + * Set the JDBC DataSource to be used by this DAO. + */ + public final void setDataSource(DataSource dataSource) { + if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + } + + public final @Nullable DataSource getDataSource() { + return (this.jdbcTemplate != null) ? this.jdbcTemplate.getDataSource() : null; + } + + /** + * Set the JdbcTemplate for this DAO explicitly, as an alternative to specifying a + * DataSource. + */ + public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + /** + * Return the JdbcTemplate for this DAO, pre-initialized with the DataSource or set + * explicitly. + */ + public final @Nullable JdbcTemplate getJdbcTemplate() { + return this.jdbcTemplate; + } + + protected void checkDaoConfig() { + if (this.jdbcTemplate == null) { + throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required"); + } + } + protected void initDao() { if (this.createTableOnStartup) { getTemplate().execute(CREATE_TABLE_SQL); @@ -128,7 +186,7 @@ public void setCreateTableOnStartup(boolean createTableOnStartup) { } private JdbcTemplate getTemplate() { - @Nullable JdbcTemplate result = super.getJdbcTemplate(); + @Nullable JdbcTemplate result = this.jdbcTemplate; if (result == null) { throw new IllegalStateException("JdbcTemplate was removed"); } diff --git a/web/src/test/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImplTests.java b/web/src/test/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImplTests.java index 71d848c7c34..58da2c17472 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImplTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/rememberme/JdbcTokenRepositoryImplTests.java @@ -48,6 +48,7 @@ /** * @author Luke Taylor + * @author Yanming Zhou */ @ExtendWith(MockitoExtension.class) public class JdbcTokenRepositoryImplTests { @@ -78,7 +79,7 @@ public void populateDatabase() { this.repo = new JdbcTokenRepositoryImpl(); ReflectionTestUtils.setField(this.repo, "logger", this.logger); this.repo.setDataSource(dataSource); - this.repo.initDao(); + this.repo.afterPropertiesSet(); this.template = this.repo.getJdbcTemplate(); this.template.execute("create table persistent_logins (username varchar(100) not null, " + "series varchar(100) not null, token varchar(500) not null, last_used timestamp not null)"); @@ -170,7 +171,7 @@ public void createTableOnStartupCreatesCorrectTable() { this.repo = new JdbcTokenRepositoryImpl(); this.repo.setDataSource(dataSource); this.repo.setCreateTableOnStartup(true); - this.repo.initDao(); + this.repo.afterPropertiesSet(); this.template.queryForList("select username,series,token,last_used from persistent_logins"); }