As for password encryption, Songko has talked with you before, please refer to:

  • Two poses for password encryption in Spring Boot!

In this article, Songo introduces two cryptographic schemes, but both are used independently! Can there be multiple password encryption schemes in the same project? The answer is yes!

Today, Songo will talk to you about how Spring Security allows many different password encryption schemes to coexist.

This is the 31st article in the Spring Security series, and reading the previous articles will help you understand it better:

  1. Dig a big hole and Spring Security will do it!
  2. How to decrypt the password
  3. A step-by-step guide to customizing form logins in Spring Security
  4. Spring Security does front and back separation, so don’t do page jumps! All JSON interactions
  5. Authorization in Spring Security used to be so simple
  6. How does Spring Security store user data into the database?
  7. Spring Security+Spring Data Jpa, Security management is only easier!
  8. Spring Boot + Spring Security enables automatic login
  9. Spring Boot automatic login. How to control security risks?
  10. How is Spring Security better than Shiro in microservices projects?
  11. Two ways for SpringSecurity to customize authentication logic (advanced play)
  12. How can I quickly view information such as the IP address of the login user in Spring Security?
  13. Spring Security automatically kicks out the previous login user.
  14. How can I kick out a user who has logged in to Spring Boot + Vue?
  15. Spring Security comes with a firewall! You have no idea how secure your system is!
  16. What is a session fixed attack? How do I defend against session fixation attacks in Spring Boot?
  17. How does Spring Security handle session sharing in a clustered deployment?
  18. Songgo hand in hand to teach you in SpringBoot CSRF attack! So easy!
  19. Learn to learn thoroughly! Spring Security CSRF defense source code parsing
  20. Two poses for password encryption in Spring Boot!
  21. How to learn Spring Security? Why must we study systematically?
  22. Spring Security has two different resource release policies. Do not use them incorrectly.
  23. Spring Boot + CAS single sign-on
  24. Spring Boot is the third way to implement single sign-on!
  25. How do I Connect to the Database in Spring Boot+CAS SINGLE Sign-on?
  26. Spring Boot+CAS default login page is too ugly, how to do?
  27. How to carry Token in request header with Swagger
  28. Summary of three cross-domain scenarios in Spring Boot
  29. How to implement HTTP authentication in Spring Boot?
  30. Four types of permission control in Spring Security

Why encrypt? Common encryption algorithm and so on these problems I will not repeat, we can refer to the previous: Spring Boot password encryption in the two posture! Let’s go straight to today’s text.

1.PasswordEncoder

In Spring Security, PasswordEncoder is responsible for all things related to password encryption/verification. PasswordEncoder has a number of implementation classes:

Some of these implementation classes are out of date or not very useful. For us, the most common one is BCryptPasswordEncoder.

PasswordEncoder is itself an interface that contains only three methods:

public interface PasswordEncoder {
	String encode(CharSequence rawPassword);
	boolean matches(CharSequence rawPassword, String encodedPassword);
	default boolean upgradeEncoding(String encodedPassword) {
		return false; }}Copy the code
  • The encode method is used to encrypt passwords.
  • The matches method is used to match passwords.
  • UpgradeEncoding Indicates whether the password needs to be encrypted again to make it more secure. The default value is false.

PasswordEncoder implements these methods.

2. Where does PasswordEncoder work

For us developers, we typically configure an instance of PasswordEncoder in SecurityConfig, something like this:

@Bean
PasswordEncoder passwordEncoder(a) {
    return new BCryptPasswordEncoder();
}
Copy the code

Everything else is called by the system. Today we are going to demystify system calls! Let’s see how the system is called!

First of all, the scene in front of the post and you mentioned that in the Spring Security, if you are using a username/password login, password is in the DaoAuthenticationProvider check, you can reference: SpringSecurity customizes authentication logic in two ways (advanced play).

We’ll look at DaoAuthenticationProvider password is how to check:

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
		throws AuthenticationException {
	if (authentication.getCredentials() == null) {
		throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials"."Bad credentials"));
	}
	String presentedPassword = authentication.getCredentials().toString();
	if(! passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {throw new BadCredentialsException(messages.getMessage(
				"AbstractUserDetailsAuthenticationProvider.badCredentials"."Bad credentials")); }}Copy the code

As you can see, password verification is done using the passwordEncoder. Matches method.

So the DaoAuthenticationProvider passwordEncoder come from? Is it the same Bean we originally configured in SecurityConfig?

We’ll look at in the DaoAuthenticationProvider about passwordEncoder definition, as follows:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	private PasswordEncoder passwordEncoder;
	public DaoAuthenticationProvider(a) {
		setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
	}
	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
		this.passwordEncoder = passwordEncoder;
		this.userNotFoundEncodedPassword = null;
	}

	protected PasswordEncoder getPasswordEncoder(a) {
		returnpasswordEncoder; }}Copy the code

Can see from this code, when DaoAuthenticationProvider create, specify the PasswordEncoder, does not seem to have used we initially configured Bean? It’s not! When DaoAuthenticationProvider create, will create a default PasswordEncoder, if we don’t have any PasswordEncoder configuration, will use the default PasswordEncoder, If we have a custom PasswordEncoder instance, our custom PasswordEncoder instance will be used!

How do you know?

We take a look at how DaoAuthenticationProvider initialization.

In InitializeUserDetailsManagerConfigurer# DaoAuthenticationProvider initialization is done in the configure method, let’s look at the definition of the method:

public void configure(AuthenticationManagerBuilder auth) throws Exception {
	if (auth.isConfigured()) {
		return;
	}
	UserDetailsService userDetailsService = getBeanOrNull(
			UserDetailsService.class);
	if (userDetailsService == null) {
		return;
	}
	PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
	UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
	DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
	provider.setUserDetailsService(userDetailsService);
	if(passwordEncoder ! =null) {
		provider.setPasswordEncoder(passwordEncoder);
	}
	if(passwordManager ! =null) {
		provider.setUserDetailsPasswordService(passwordManager);
	}
	provider.afterPropertiesSet();
	auth.authenticationProvider(provider);
}
Copy the code

From this code we can see:

  1. Get an instance of PasswordEncoder by calling getBeanOrNull, which essentially looks for objects in the Spring container.
  2. The next new a DaoAuthenticationProvider object directly, as we all know, in the process of new DaoAuthenticationProvider PasswordEncoder default has been created.
  3. If at first obtained from the Spring container to PasswordEncoder instance, will the assigned to DaoAuthenticationProvider instance, Or use DaoAuthenticationProvider PasswordEncoder created by default.

At this point, it’s clear that the PasswordEncoder instance we configured does work.

3. What’s the default?

And as you can see, if we don’t do any configuration, the default PasswordEncoder will also be provided, so what’s the default PasswordEncoder? Let’s start with this method:

public DaoAuthenticationProvider(a) {
	setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
Copy the code

Continue to:

public class PasswordEncoderFactories {
	public static PasswordEncoder createDelegatingPasswordEncoder(a) {
		String encodingId = "bcrypt";
		Map<String, PasswordEncoder> encoders = new HashMap<>();
		encoders.put(encodingId, new BCryptPasswordEncoder());
		encoders.put("ldap".new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
		encoders.put("MD4".new org.springframework.security.crypto.password.Md4PasswordEncoder());
		encoders.put("MD5".new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
		encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
		encoders.put("pbkdf2".new Pbkdf2PasswordEncoder());
		encoders.put("scrypt".new SCryptPasswordEncoder());
		encoders.put("SHA-1".new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
		encoders.put("SHA-256".new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
		encoders.put("sha256".new org.springframework.security.crypto.password.StandardPasswordEncoder());
		encoders.put("argon2".new Argon2PasswordEncoder());

		return new DelegatingPasswordEncoder(encodingId, encoders);
	}

	private PasswordEncoderFactories(a) {}}Copy the code

You can see:

  1. In PasswordEncoderFactories, you first build an Encoders, then give all the encoders a name, then store the name as a key and the encoding method as a value into the Encoders.
  2. Finally returned to a DelegatingPasswordEncoder instance, at the same time is introduced into the default encodingId bcrypt, and examples of encoders, DelegatingPasswordEncoder name should be a proxy object.

We’ll look at the definition of DelegatingPasswordEncoder:

public class DelegatingPasswordEncoder implements PasswordEncoder {
	private static final String PREFIX = "{";
	private static final String SUFFIX = "}";
	private final String idForEncode;
	private final PasswordEncoder passwordEncoderForEncode;
	private final Map<String, PasswordEncoder> idToPasswordEncoder;
	private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
	public DelegatingPasswordEncoder(String idForEncode, Map
       
         idToPasswordEncoder)
       ,> {
		if (idForEncode == null) {
			throw new IllegalArgumentException("idForEncode cannot be null");
		}
		if(! idToPasswordEncoder.containsKey(idForEncode)) {throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
		}
		for (String id : idToPasswordEncoder.keySet()) {
			if (id == null) {
				continue;
			}
			if (id.contains(PREFIX)) {
				throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
			}
			if (id.contains(SUFFIX)) {
				throw new IllegalArgumentException("id " + id + " cannot contain "+ SUFFIX); }}this.idForEncode = idForEncode;
		this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
		this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder);
	}
	public void setDefaultPasswordEncoderForMatches( PasswordEncoder defaultPasswordEncoderForMatches) {
		if (defaultPasswordEncoderForMatches == null) {
			throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
		}
		this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
	}

	@Override
	public String encode(CharSequence rawPassword) {
		return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
	}

	@Override
	public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
		if (rawPassword == null && prefixEncodedPassword == null) {
			return true;
		}
		String id = extractId(prefixEncodedPassword);
		PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
		if (delegate == null) {
			return this.defaultPasswordEncoderForMatches
				.matches(rawPassword, prefixEncodedPassword);
		}
		String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
		return delegate.matches(rawPassword, encodedPassword);
	}

	private String extractId(String prefixEncodedPassword) {
		if (prefixEncodedPassword == null) {
			return null;
		}
		int start = prefixEncodedPassword.indexOf(PREFIX);
		if(start ! =0) {
			return null;
		}
		int end = prefixEncodedPassword.indexOf(SUFFIX, start);
		if (end < 0) {
			return null;
		}
		return prefixEncodedPassword.substring(start + 1, end);
	}

	@Override
	public boolean upgradeEncoding(String prefixEncodedPassword) {
		String id = extractId(prefixEncodedPassword);
		if (!this.idForEncode.equalsIgnoreCase(id)) {
			return true;
		}
		else {
			String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
			return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword); }}private String extractEncodedPassword(String prefixEncodedPassword) {
		int start = prefixEncodedPassword.indexOf(SUFFIX);
		return prefixEncodedPassword.substring(start + 1);
	}
	private class UnmappedIdPasswordEncoder implements PasswordEncoder {

		@Override
		public String encode(CharSequence rawPassword) {
			throw new UnsupportedOperationException("encode is not supported");
		}

		@Override
		public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
			String id = extractId(prefixEncodedPassword);
			throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\" "); }}}Copy the code

This code is quite long, so LET me explain it to you one by one:

  1. DelegatingPasswordEncoder is realized PasswordEncoder interface, so it inside the core of the method are two: encode method is used to encode the password, matches method is used to check the password.
  2. In DelegatingPasswordEncoder constructor, through incoming two parameters encodingId and encoders, access to the assigned to the default encoder passwordEncoderForEncode, The default encoder is actually BCryptPasswordEncoder.
  3. We encode passwords in an encode method, but we encode them in a way that prefixes them{encoder name}For example, if you encode using BCryptPasswordEncoder, the resulting password will look similar{bcrypt}$2a$10$oE39aG10kB/rFu2vQeCJTu/V/v4n6DRR0f8WyXRiAYvBpmadoOBE.. What’s the use of that? After each password is encrypted, a prefix is added to it. In this way, you can know which encoder is used to generate the ciphertext.
  4. The matches method extracts the prefix from the ciphertext, finds the corresponding PasswordEncoder based on the prefix, and then calls the Matches method of PasswordEncoder to match passwords.
  5. If according to the extracted prefix, can’t find the corresponding PasswordEncoder, then is called UnmappedIdPasswordEncoder# matches method, to carry on the password comparison, this method does not actually code, just to throw an exception.

OK, now, I believe everyone understand the working principle of DelegatingPasswordEncoder.

If we want to use multiple password encryption schemes at the same time, it seems it is ok to use DelegatingPasswordEncoder, while DelegatingPasswordEncoder also need not configured by default.

Experience 4.

Next, we experience a little bit about the DelegatingPasswordEncoder usage.

First let’s generate three passwords as test passwords:

@Test
void contextLoads(a) {
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put("bcrypt".new BCryptPasswordEncoder());
    encoders.put("MD5".new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
    encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
    DelegatingPasswordEncoder encoder1 = new DelegatingPasswordEncoder("bcrypt", encoders);
    DelegatingPasswordEncoder encoder2 = new DelegatingPasswordEncoder("MD5", encoders);
    DelegatingPasswordEncoder encoder3 = new DelegatingPasswordEncoder("noop", encoders);
    String e1 = encoder1.encode("123");
    String e2 = encoder2.encode("123");
    String e3 = encoder3.encode("123");
    System.out.println("e1 = " + e1);
    System.out.println("e2 = " + e2);
    System.out.println("e3 = " + e3);
}
Copy the code

The result is as follows:

e1 = {bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi
e2 = {MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2
e3 = {noop}123
Copy the code

Next, let’s copy these three passwords into SecurityConfig:

@Configuration("aaa")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    protected UserDetailsService userDetailsService(a) {

        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("javaboy").password("{bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi").roles("admin").build());
        manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build());
        manager.createUser(User.withUsername("A little Rain in the South").password("{MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2").roles("user").build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")... }}Copy the code

Here, three users use three different password encryption methods.

After the configuration is complete, restart the project and log in using Javaboy /123, Sang /123, and Jiangnan Little Rain /123 respectively.

5. What’s the point?

Why do we have this need? Want to have multiple password encryption schemes in your project? Actually the old project mainly for the transform, password encryption once established, basically can’t change again (you can’t let users register it again), but we also want to use the latest framework to do password encryption, so there is no doubt that DelegatingPasswordEncoder is the best choice.

Ok, this is today and partners to share a variety of password encryption scheme problems, interested partners remember to point to encourage songge oh ~