PasswordChangeService.java
package com.taxonomy.security.service;
import com.taxonomy.security.model.AppUser;
import com.taxonomy.security.repository.UserRepository;
import org.springframework.context.annotation.Profile;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/** Domain service for validating and applying local-user password changes. */
@Service
@Profile("!keycloak")
public class PasswordChangeService {
public static final int MINIMUM_PASSWORD_LENGTH = 12;
public enum Result {
CHANGED,
USER_NOT_FOUND,
CURRENT_PASSWORD_INCORRECT,
TOO_SHORT,
CONFIRMATION_MISMATCH,
SAME_AS_CURRENT
}
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final BootstrapAdminCredentialStore bootstrapCredentialStore;
public PasswordChangeService(UserRepository userRepository,
PasswordEncoder passwordEncoder,
BootstrapAdminCredentialStore bootstrapCredentialStore) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.bootstrapCredentialStore = bootstrapCredentialStore;
}
@Transactional(readOnly = true)
public boolean isPasswordChangeRequired(String username) {
return username != null && userRepository.findByUsername(username)
.map(AppUser::isMustChangePassword)
.orElse(false);
}
@Transactional
public Result changePassword(String username,
String currentPassword,
String newPassword,
String confirmPassword) {
AppUser user = userRepository.findByUsername(username).orElse(null);
if (user == null) {
return Result.USER_NOT_FOUND;
}
if (currentPassword == null
|| !passwordEncoder.matches(currentPassword, user.getPasswordHash())) {
return Result.CURRENT_PASSWORD_INCORRECT;
}
if (newPassword == null || newPassword.length() < MINIMUM_PASSWORD_LENGTH) {
return Result.TOO_SHORT;
}
if (!newPassword.equals(confirmPassword)) {
return Result.CONFIRMATION_MISMATCH;
}
if (newPassword.equals(currentPassword)) {
return Result.SAME_AS_CURRENT;
}
user.setPasswordHash(passwordEncoder.encode(newPassword));
user.setMustChangePassword(false);
userRepository.save(user);
removeBootstrapCredentialAfterCommit(username);
return Result.CHANGED;
}
private void removeBootstrapCredentialAfterCommit(String username) {
if (!"admin".equals(username)) {
return;
}
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
bootstrapCredentialStore.deletePublishedCredential();
return;
}
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
bootstrapCredentialStore.deletePublishedCredential();
}
});
}
}