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 5856580cce..951e3d25ab 100644 --- a/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java +++ b/core/src/main/java/org/springframework/security/provisioning/JdbcUserDetailsManager.java @@ -46,6 +46,7 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.cache.NullUserCache; import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl; import org.springframework.util.Assert; @@ -65,7 +66,8 @@ import org.springframework.util.Assert; * @author Luke Taylor * @since 2.0 */ -public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager, GroupManager { +public class JdbcUserDetailsManager extends JdbcDaoImpl + implements UserDetailsManager, GroupManager, UserDetailsPasswordService { public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)"; @@ -162,6 +164,8 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa private RowMapper grantedAuthorityMapper = this::mapToGrantedAuthority; + private boolean enableUpdatePassword = false; + public JdbcUserDetailsManager() { } @@ -591,6 +595,20 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa this.userCache = userCache; } + /** + * Sets whether the {@link #updatePassword(UserDetails, String)} method should + * actually update the password. + *

+ * Defaults to {@code false} to prevent accidental password updates that might produce + * passwords that are too large for the current database schema. Users must explicitly + * set this to {@code true} to enable password updates. + * @param enableUpdatePassword {@code true} to enable password updates, {@code false} + * otherwise. + */ + public void setEnableUpdatePassword(boolean enableUpdatePassword) { + this.enableUpdatePassword = enableUpdatePassword; + } + private void validateUserDetails(UserDetails user) { Assert.hasText(user.getUsername(), "Username may not be empty or null"); validateAuthorities(user.getAuthorities()); @@ -604,4 +622,14 @@ public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsMa } } + @Override + public UserDetails updatePassword(UserDetails user, String newPassword) { + if (this.enableUpdatePassword) { + UserDetails updated = User.withUserDetails(user).password(newPassword).build(); + updateUser(updated); + return updated; + } + return user; + } + } 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 5c8ed72b18..db49411c25 100644 --- a/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java +++ b/core/src/test/java/org/springframework/security/provisioning/JdbcUserDetailsManagerTests.java @@ -410,6 +410,23 @@ public class JdbcUserDetailsManagerTests { verify(mockMapper).mapRow(any(), anyInt()); } + @Test + void updatePasswordWhenDisabledReturnOriginalUser() { + insertJoe(); + this.manager.updatePassword(joe, "new"); + UserDetails newJoe = this.manager.loadUserByUsername("joe"); + assertThat(newJoe.getPassword()).isEqualTo("password"); + } + + @Test + void updatePasswordWhenEnabledShouldUpdatePassword() { + insertJoe(); + this.manager.setEnableUpdatePassword(true); + this.manager.updatePassword(joe, "new"); + UserDetails newJoe = this.manager.loadUserByUsername("joe"); + assertThat(newJoe.getPassword()).isEqualTo("new"); + } + private Authentication authenticateJoe() { UsernamePasswordAuthenticationToken auth = UsernamePasswordAuthenticationToken.authenticated("joe", "password", joe.getAuthorities());