The coexistence of multiple encryption schemes of spring security is a powerful tool for the integration of old and dilapidated systems!

Time:2021-7-22

About the problem of password encryption, brother song has talked with you before

  • Two postures of password encryption in spring boot!

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

Today, brother song will talk with you about how to make a variety of different password encryption schemes coexist in spring security.

This article is the 31st in the spring security series. Reading the previous articles will help you better understand this article

  1. Dig a big hole, spring security!
  2. SongGe takes you to spring security by hand. Don’t ask how to decrypt the password
  3. Hand in hand to teach you to customize the form login in spring security
  4. Spring security to do before and after the end of the separation, we do not do the page Jump! All JSON interaction
  5. The authorization operation in spring security is so simple
  6. How does spring security store user data into a database?
  7. The combination of spring security and spring data JPA makes security management easier!
  8. Automatic login function with spring boot + spring security
  9. How to control the security risk of spring boot auto login?
  10. In microservice projects, where is spring security better than Shiro?
  11. Two ways of spring security user defined authentication logic (advanced play)
  12. How to quickly view the login user’s IP address and other information in spring security?
  13. Spring security automatically kicks off the previous login user, one configuration is done!
  14. Spring boot + Vue front and back end separation project, how to kick off the logged in users?
  15. Spring security comes with firewall! You don’t know how secure your system is!
  16. What is session fixation attack? How to defend against fixed session attack in spring boot?
  17. How does spring security deal with session sharing in cluster deployment?
  18. SongGe teaches you how to defend against CSRF attack in springboot! so easy!
  19. Learn thoroughly if you want to! Source code analysis of CSRF defense in spring security
  20. Two postures of password encryption in spring boot!
  21. How to learn spring security? Why must we study systematically?
  22. Spring security two resource release strategies, don’t use them wrong!
  23. Spring boot + CAS single sign on
  24. Spring boot implements the third scheme of single sign on!
  25. Spring boot + CAS single sign on, how to dock database?
  26. The default login page of spring boot + CAS is too ugly. What should I do?
  27. Using swagger to test the interface, how to carry the token in the request header?
  28. Summary of three cross domain scenarios in spring boot
  29. How to implement HTTP authentication in spring boot?
  30. Four access control methods in spring security

Why encrypt? I won’t repeat the common encryption algorithms. You can refer to the previous two postures of password encryption in spring boot!, Let’s go straight to today’s text.

1.PasswordEncoder

In spring security, everything related to password encryption / verification is dominated by passwordencoder, which has many implementation classes

The coexistence of multiple encryption schemes of spring security is a powerful tool for the integration of old and dilapidated systems!

Some of these implementation classes have expired, and some are not very useful. For us, the most commonly used is bcryptpasswordencoder.

Passwordencoder itself is an interface with only three methods

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

The implementation class of passwordencoder implements these methods.

2. Where does passwordencoder work

For our developers, we usually configure an instance of passwordencoder in securityconfig, similar to the following:

@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

The rest is called by the system. Today, we will unveil the mystery of system call! Let’s see how the system is called!

First of all, SongGe mentioned in the previous article that in spring security, if you log in with a user name / password, the password is verified in the daoauthentication provider. You can refer to two ways of spring security custom authentication logic (advanced play).

Let’s take a look at how the password in daoauthenticationprovider is verified

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"));
    }
}

As you can see, password verification is done through the passwordencoder. Matches method.

So where does the passwordencoder in daoauthentication provider come from? Is it the bean that we configured in securityconfig at the beginning?

Let’s take a look at the definition of passwordencoder in daoauthentication provider, as follows:

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

    protected PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }
}

As you can see from this code, passwordencoder was specified when the daoauthentication provider was created. It seems that we did not use the bean? Not really! When the daoauthenticationprovider is created, a default passwordencoder will be set. If we do not configure any passwordencoder, we will use the default passwordencoder. If we customize the passwordencoder instance, we will use our custom passwordencoder instance!

How to know?

Let’s see how daoauthenticationprovider is initialized.

The initialization of daoauthenticationprovider is completed in the initializeuserdetailsmanagerconfigurer? Configure method. Let’s take a look at the definition of this 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);
}

We can see from this Code:

  1. First, call the getbeanornull method to get an instance of passwordencoder. The getbeanornull method is actually to find an object in the spring container.
  2. Next, create a daoauthenticationprovider object directly. As you know, in the process of new, the default passwordencoder in daoauthenticationprovider has been created.
  3. If the passwordencoder instance is obtained from the spring container at the beginning, it will be assigned to the daoauthenticationprovider instance. Otherwise, it is the passwordencoder created by daoauthenticationprovider itself by default.

So far, the truth is clear, and the passwordencoder instance we configured is actually used.

3. What is the default?

At the same time, you can see that if we do not make any configuration, the default passwordencoder will also be provided. What is the default passwordencoder? Let’s start with this method

public DaoAuthenticationProvider() {
    setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}

continue:

public class PasswordEncoderFactories {
    public static PasswordEncoder createDelegatingPasswordEncoder() {
        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() {}
}

We can see that:

  1. In passwordencoderfactors, we first build an encoders, then give all the encoding methods a name, and then make the name key and the encoding method value, and store them in encoders.
  2. Finally, an instance of delegatingpasswordencoder is returned. At the same time, the default encoding ID is bcrypt and the instances of encoders are passed in. The delegatingpasswordencoder should be a proxy object.

Let’s 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<String, PasswordEncoder> 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 + "\"");
        }
    }
}

This code is quite long. Let me explain to you one by one:

  1. Delegatingpasswordencoder also implements the passwordencoder interface, so there are two core methods in it: encode method is used to encode the password, and matches method is used to verify the password.
  2. In the construction method of delegating passwordencoder, the default encoder is obtained by passing in two parameters encoding ID and encoders, and assigned to passwordencoder for encode. The default encoder is actually bcryptpasswordencoder.
  3. In the encode method, the password is encoded, but the encoding method is prefixed, and the prefix is{encoder name}For example, if you code with bcryptpasswordencoder, the generated password is similar{bcrypt}$2a$10$oE39aG10kB/rFu2vQeCJTu/V/v4n6DRR0f8WyXRiAYvBpmadoOBE.。 What’s the use of this? After each password is encrypted, a prefix will be added, so that you can see the prefix and know which encoder the ciphertext is generated by.
  4. Finally, the logic of the matches method is very clear. First extract the prefix from the ciphertext, and then find the corresponding passwordencoder according to the prefix, and then call the matches method of passwordencoder for password comparison.
  5. If the corresponding passwordencoder cannot be found according to the extracted prefix, the unmappedidpasswordencoder # matches method will be called to perform password comparison. In fact, this method does not perform password comparison, but directly throws an exception.

OK, so far, I believe you all understand the working principle of delegating password encoder.

If we want to use multiple password encryption schemes at the same time, it seems that using delegating passwordencoder is OK, and delegating passwordencoder does not need to be configured by default.

4. Experience

Next, let’s experience the usage of delegatingpasswordencoder.

First, we generate three passwords as test passwords

@Test
void contextLoads() {
    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);
}

The results are as follows:

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

Next, we copy these three passwords into securityconfig

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

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {

        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 Jiangnan"). Password ("{MD5} {wucj / l8wmtmtzfi3obkwsetnexbmfahzw9vck9mahmhc =} 4d43db282b36d7f0421498fdc693f2a2"). Roles ("user")));
        return manager;
    }

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

Here three users use three different ways of password encryption.

After the configuration is completed, restart the project and log in using javaboy / 123, sang / 123 and Jiangnan yidianyu / 123 respectively. It is found that the login is successful.

5. What is the significance?

Why do we have this demand? Do you want to have multiple password encryption schemes at the same time? In fact, this is mainly used for the transformation of old projects. Once the password encryption method is determined, it can’t be changed (you can’t let users register again), but we want to use the latest framework to do password encryption. Undoubtedly, delegating passwordencoder is the best choice.

Well, this is the problem of multiple password encryption schemes shared with my friends today. If you are interested, please remember to click and encourage brother song

Recommended Today

Comparison of view design, element plus and Ant Design Vue component libraries

usevue3.0Initialize the project and tangle with the selected component libraryelement-plusstillAnt Design Vue, so the following comparison is made (withview-design vue2.0Comparison): Access address view-design:http://v1.iviewui.com/docs/introduce element-plus:https://element-plus.gitee.io/#/zh-CN/component/installation Ant Design Vue:https://www.antdv.com/docs/vue/introduce-cn/ Comparison term Indexes Comparison item view-design element-plus Ant Design Vue 1 vue2.0 Y N N 2 vue3.0 N Y Y 3 star/fork 23.8k/4.2k 10.6k/1.1k 14.7k/2.5k 4 IE11 Y […]