Summary

org.springframework.security.crypto.password.PasswordEncoderFactories is inflexible as it impose bcrypt as default encoder id for the created DelegatingPasswordEncoder

Actual Behavior

PasswordEncoderFactories is not customizable it has only one static method returning a PasswordEncoder instead DelegatingPasswordEncoder:
public static PasswordEncoder createDelegatingPasswordEncoder()

Expected Behavior

My proposal as I havent' fork your project to make a pull request

package org.mitre.security.crypto.password;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
import org.springframework.security.crypto.password.Md4PasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

/**
 * A {@link DelegatingPasswordEncoder} factory with default mappings. Additional
 * mappings may be added using {@link #register(String, PasswordEncoder)} and
 * the encoding will be updated to conform with best practices. However, due to
 * the nature of {@link DelegatingPasswordEncoder} the updates should not impact
 * users. The mappings current are:
 *
 * <ul>
 * <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
 * <li>ldap - {@link LdapShaPasswordEncoder}</li>
 * <li>md4 - {@link Md4PasswordEncoder}</li>
 * <li>md5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
 * <li>noop - {@link NoOpPasswordEncoder}</li>
 * <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
 * <li>scrypt - {@link SCryptPasswordEncoder}</li>
 * <li>sha-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
 * <li>sha-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
 * <li>sha256 - {@link StandardPasswordEncoder}</li>
 * <li>argon2 - {@link Argon2PasswordEncoder}</li>
 * </ul>
 * 
 * @author Rob Winch
 * @author Hichem BOURADA
 * @since 5.3
 */
public class DelegatingPasswordEncoderFactory {

    private final Map<String, PasswordEncoder> encoders = new HashMap<>();

    public DelegatingPasswordEncoderFactory() {
        this.registerDefaultEncoders();
    }

    public DelegatingPasswordEncoderFactory(Map<String, PasswordEncoder> encoders) {
        this.encoders.putAll(encoders);
    }

    public DelegatingPasswordEncoderFactory register(String name, PasswordEncoder encoder) {
        this.encoders.put(Objects.requireNonNull(name), Objects.requireNonNull(encoder));
        return this;
    }

    protected void registerDefaultEncoders() {
        for (DefaultEncoders encoder : DefaultEncoders.values()) {
            this.register(encoder.encoderName, encoder.encoder);
        }
    }

    public DelegatingPasswordEncoder create(String encoderId) {
        return new DelegatingPasswordEncoder(encoderId, new HashMap<>(encoders));
    }

    @SuppressWarnings("deprecation")
    public enum DefaultEncoders {
        BCRYPT("bcrypt", new BCryptPasswordEncoder()), //
        LDAP("ldap", new LdapShaPasswordEncoder()), //
        MD4("md4", new Md4PasswordEncoder()), //
        MD5("md5", new MessageDigestPasswordEncoder("MD5")), //
        NOOP("noop", NoOpPasswordEncoder.getInstance()), //
        PBKDF2("pbkdf2", new Pbkdf2PasswordEncoder()), //
        SCRYPT("scrypt", new SCryptPasswordEncoder()), //
        SHA_1("sha-1", new MessageDigestPasswordEncoder("SHA-1")), //
        SHA_256("sha-256", new MessageDigestPasswordEncoder("SHA-256")), //
        SHA256("sha256", new StandardPasswordEncoder()), //
        ARGON2("argon2", new Argon2PasswordEncoder());

        public final String encoderName;

        public final PasswordEncoder encoder;

        DefaultEncoders(String encoderName, PasswordEncoder encoder) {
            this.encoderName = encoderName;
            this.encoder = encoder;
        }

    }
}

Configuration

Version

5.2

Sample

Comment From: eleftherias

Thanks for the report @hbourada.

This is a duplicate of gh-5939. You can check out that issue for a workaround.