Password Encoder in Spring Security
In this post, We will take a look at password encoders in detail with an example. Traditionally, storing passwords were hard. The application will have to encode user passwords and store them in a database.
But with password encoders provided by spring security, all of these can be done automatically. Password Encoders are beans that transform plain text password into hashes. As the hashes cannot be reversed into plaintext, it is a secure way to store passwords.
Password Hashing
To begin with, Hashing algorithms take a sequence of bytes and turn into a unique fixed-length hash string.
Hashing algorithms are one-way functions and cannot be reversed. This means the original plain text cannot be generated back from a hash.
This property makes the hashing viable for storing passwords.
Spring Security Password Encoder
For the password encoding/hashing, Spring Security expects a password encoder implementation. Also, it provides dogmatic implementations based on industry standards. These encoders will be used in the password storing phases and validation phase of authentication.
The passwordEncoders have two main tasks. They are,
encoder.encode(String rawPassword)
– converts a given plaintext password into an encoded password. And how it converts is up to the implementation. This part happens at the time when the password is stored in the DB. Usually when registering a user or changing the password.encoder.matches(rawPassword, encodedPassword)
– Used whenever login happens. Security context will load the encrypted password from the database and use this method to compare against the raw password.
Here is how the encoders play role in the registration process.
The following diagram illustrates how Spring Security uses encoder for validating the login password.
now let’s see some examples.
Registration with password encoders
At the time of registration, you need to encode the password before storing it in the database. For this, you need to define a bean of type PasswordEncoder
. At the time of writing the best implementation is to use the BCrypt algorithm. Here is how you can do it.
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
Code language: JavaScript (javascript)
And somewhere in your registration API endpoint, you will have to autowire this bean. Here is a simple example.
@RestController
public class RegistrationController {
private final UserAccountRepository userAccountRepository;
private final PasswordEncoder passwordEncoder;
public UserController(UserAccountRepository userAccountRepository, PasswordEncoder passwordEncoder) {
this.userAccountRepository = userAccountRepository;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("/register")
public UserAccount register(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("firstName") String firstName, @RequestParam("lastName") String lastName) {
UserAccount userAccount = new UserAccount();
userAccount.setFirstName(firstName);
userAccount.setLastName(lastName);
userAccount.setUsername(username);
userAccount.setActive(true);
userAccount.setPassword(passwordEncoder.encode(password));
return userAccountRepository.save(userAccount);
}
}
Code language: PHP (php)
You can check out git repository for the full implementation at the end of this post. In this example we registered the user with the help of BCryptPasswordEncoder. You can see how the passwords had been encoded in the below picture.
Migrating passwords from Spring Security 4
If you are upgrading from spring security4, then you need to make sure that the current passwords are encrypted using the same algorithm. Here is a sample code to convert plaintext passwords to hashes.
public class BCryptConverter {
public static void main(String[] args) {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
System.out.println(bCryptPasswordEncoder.encode("Hello@123"));
System.out.println(bCryptPasswordEncoder.encode("Hello#123"));
}
}
Code language: JavaScript (javascript)
Delegating Password Encoder
There may be situations if you wanted to use multiple types of encoders within the same data source. For example, MD5, SHA-256, pbkdf2 are some common password hashing functions. For this reason, spring offers a DelegatingPasswordEncoder
.
This encoder relies on other password encoders by routing the requests based on a password prefix. To use this, you need to make some changes to our previous arrangement.
You need to create the bean for type DelegatingPasswordEncoder
instead of BCryptPasswordEncoder
and you can do this easily with the help of PasswordEncoderFactories
class.
@Bean
PasswordEncoder passwordEncoder(){
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Code language: CSS (css)
This delegating encoder encodes with bcrypt algorithm by default. This is why the password stored in the database will be prepended with the text {bcrypt}
. This prepended information will be used to identify the appropriate passwordEncoder when encoder.matches()
method is called.
At the time of writing the default mapping for encoding type is as shown below.
EncodingId | Implementation to match |
---|---|
bcrypt | new org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder(); |
ldap | new org.springframework.security.crypto.password.LdapShaPasswordEncoder(); |
MD4 | new org.springframework.security.crypto.password.Md4PasswordEncoder(); |
MD5 | new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“MD5”); |
noop | new org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance(); |
pbkdf2 | new org.springframework.security.crypto.password.Pbkdf2PasswordEncoder(); |
scrypt | new org.springframework.security.crypto.scrypt.SCryptPasswordEncoder(); |
SHA-1 | new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“SHA-1”); |
SHA-256 | new org.springframework.security.crypto.password.MessageDigestPasswordEncoder(“SHA-256”); |
sha256 | new org.springframework.security.crypto.password.StandardPasswordEncoder(); |
argon2 | new org.springframework.security.crypto.argon2.Argon2PasswordEncoder(); |
Customizing DelegatingPasswordEncoder
You can customize the list of supported encoding types by creating the DelegatingPasswordEncoder by your own. For example, The following would only support MD5, bcrypt and plainText(noop) encoding.
@Bean
PasswordEncoder passwordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("bcrypt", new BCryptPasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
return new DelegatingPasswordEncoder("bcrypt", encoders);
}
Code language: JavaScript (javascript)
Migrating old plaintext passwords to bcyrpt
The delegating encoder will allow both encoded and plain text passwords to co-exist. However, there is still a security risk for those passwords which are not encoded. In these situations, Write a program that will convert all plain text passwords to encoded strings. Here is a sample for you to try with.
public class BCryptConvert {
public static void main(String[] args) {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
System.out.println(passwordEncoder.encode("Hello@123"));
System.out.println(passwordEncoder.encode("Hello#123"));
}
}
Code language: JavaScript (javascript)
GitHub
You can find these examples at out GitHub repository.