DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • Component Tests for Spring Cloud Microservices
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC

Trending

  • How AI Is Rewriting Full-Stack Java Systems: Practical Patterns with Spring Boot, Kafka and WebSockets
  • Code Quality Had 5 Pillars. AI Broke 3 and Created 2 We Can’t Measure
  • Stop Writing Dialect-Specific SQL: A Unified Query Builder for Node.js
  • Why Your QA Engineer Should Be the Most Stubborn Person on the Team
  1. DZone
  2. Coding
  3. Frameworks
  4. Password Encoder Migration With Spring Security 5

Password Encoder Migration With Spring Security 5

Learn more about implementing the password encoder migration with Spring Security 5.

By 
Marcos Barbero user avatar
Marcos Barbero
·
Feb. 07, 19 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
37.1K Views

Join the DZone community and get the full member experience.

Join For Free

Recently, I was working on a project that used the custom PasswordEncoder, and there was a requirement to migrate it to bcrypt. The current passwords are stored as a hash, which means that it’s not possible to revert it to the original String — at least not in an easy way.

The challenge here was how to support both implementations, the old hash solution along with the new bcrypt implementation. After a little research, I could find Spring Security 5’sDelegatingPasswordEncoder.

Meet DelegatingPasswordEncoder

The DelegatingPasswordEncoder class makes it possible to support multiple password encoders based on a prefix. The password is stored like this:

{bcrypt}$2a$10$vCXMWCn7fDZWOcLnIEhmK.74dvK1Eh8ae2WrWlhr2ETPLoxQctN4.
{noop}plaintextpassword


Spring Security 5 brings the handy PasswordEncoderFactories class; currently, this class supports the following encoders:

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());

    return new DelegatingPasswordEncoder(encodingId, encoders);
}


Now, instead of declaring a single PasswordEncoder, we can use the PasswordEncoderFactories, like this snippet of code:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}


Adding a Custom Encoder

Now, getting back to my initial problem, for legacy reasons, there is a homegrown password encoding solution, and the handy PasswordEncoderFactories knows nothing about it, to solve that I’ve created a class similar to thePasswordEncoderFactories, and I’ve added all the built-in encoders along with my custom one; here’s a sample implementation:

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

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

class DefaultPasswordEncoderFactories {

    @SuppressWarnings("deprecation")
    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("custom", new CustomPasswordEncoder());

        return new DelegatingPasswordEncoder(encodingId, encoders);
    }
}


And then, I declared my @Bean using the DefaultPasswordEncoderFactories instead.

After my first run, I realized another problem: I would have to run a SQL script to update all the existing passwords adding the {custom} prefix so the framework could properly bind the prefix with the right PasswordEncoder— don’t get me wrong; it’s a fine solution, but I really did not want to mess around with existing passwords in the database. And luckily, for us, the DelegatingPasswordEncoder class allows us to set a defaultPasswordEncoder. It means that whenever the framework tries and doesn’t find a prefix in the stored password, it will fall back to the default one to try to decode it.

Then, I changed my implementation to the following:


import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

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

class DefaultPasswordEncoderFactories {

    @SuppressWarnings("deprecation")
    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"));

        DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new CustomPasswordEncoder());
        return delegatingPasswordEncoder;
    }

}


And the @Bean declaration is now:

@Bean
public PasswordEncoder passwordEncoder() {
    return DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder();
}


Conclusion

Migration password encoders is a real-life problem and Spring Security 5 gives a quite handy way to easily handle it by supporting multiple PasswordEncoders at once.

Footnote

  • The code used for this tutorial can be found on GitHub.
  • DelegatingPasswordEncoder - Spring Docs
Spring Security Spring Framework

Published at DZone with permission of Marcos Barbero. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Enterprise RIA With Spring 3, Flex 4 and GraniteDS
  • Component Tests for Spring Cloud Microservices
  • Authentication With Remote LDAP Server in Spring WebFlux
  • Authentication With Remote LDAP Server in Spring Web MVC

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook