In the previous article, we learned about authentication and authorization and implemented role-based access control using Spring Security 7.
However, storing passwords securely is equally important. Saving plain-text passwords is one of the biggest security risks in any application. If an attacker gains access to the database, all user credentials become exposed.
Spring Security provides powerful password encoding mechanisms that help us securely store passwords and protect user accounts.
In this article, we will understand password encoding, BCryptPasswordEncoder, DelegatingPasswordEncoder, and production-level password security practices.
Why Password Encoding is Important
Consider a User table:
Username | Password |
|---|---|
john | password123 |
admin | admin123 |
If the database gets compromised, attackers can immediately see every user's password.
Instead, passwords should be stored as hashes:
Username | Password |
|---|---|
john | $2a$10$U7df... |
admin | $2a$10$P89a... |
Even if the database is leaked, original passwords cannot easily be recovered.
What is Password Encoding?
Password encoding converts a plain password into a hashed value.
Plain password:
password123
Encoded password:
$2a$10$zAxK4R6P9gVh6R...
During login:
User enters password.
Spring Security encodes it internally.
The encoded value is compared with the stored hash.
Authentication succeeds if both match.
Understanding PasswordEncoder
Spring Security provides the PasswordEncoder interface.
publicinterfacePasswordEncoder {
Stringencode(CharSequencerawPassword);
booleanmatches(CharSequencerawPassword,
StringencodedPassword);
}
encode()
Converts plain password into a secure hash.
matches()
Compares the raw password with the stored hash.
BCryptPasswordEncoder
The most commonly used implementation is:
@Bean
publicPasswordEncoderpasswordEncoder() {
returnnewBCryptPasswordEncoder();
}
BCrypt provides:
Salting
Hashing
Adaptive strength
Protection against rainbow table attacks
It is recommended for most production applications.
Encoding Passwords
PasswordEncoderencoder=newBCryptPasswordEncoder();
StringencodedPassword=
encoder.encode("password123");
System.out.println(encodedPassword);
Output:
$2a$10$SDF78asd89Jks...
Notice that every execution generates a different value.
Why Does BCrypt Generate Different Hashes?
Example:
encoder.encode("password123");
encoder.encode("password123");
Output:
$2a$10$A4gf...
$2a$10$L9df...
Both hashes are different.
This happens because BCrypt uses a random salt internally.
This protects against precomputed dictionary attacks.
Verifying Passwords
PasswordEncoderencoder=
newBCryptPasswordEncoder();
Stringhash=
encoder.encode("password123");
booleanmatched=
encoder.matches(
"password123",
hash);
System.out.println(matched);
Output:
true
Spring Security compares the original password with the hash.
Using PasswordEncoder in UserDetailsService
@Bean
publicUserDetailsServiceuserDetailsService() {
UserDetailsuser=User.builder()
.username("john")
.password(passwordEncoder()
.encode("password123"))
.roles("USER")
.build();
returnnewInMemoryUserDetailsManager(user);
}
Without encoding:
.password("password123")
Spring Security throws:
There is no PasswordEncoder mapped for id "null"
Therefore, passwords should always be encoded.
Understanding DelegatingPasswordEncoder
Spring Security internally uses DelegatingPasswordEncoder.
Example:
{bcrypt}$2a$10$8fdK...
The prefix indicates which encoder was used.
Supported algorithms include:
bcrypt
pbkdf2
scrypt
noop
This allows migration between algorithms without breaking existing users.
Creating PasswordEncoder Bean
@Configuration
publicclassSecurityConfig {
@Bean
publicPasswordEncoderpasswordEncoder() {
returnnewBCryptPasswordEncoder();
}
}
Spring automatically injects this bean wherever required.
Real-World Example
Suppose users register through an API.
Request:
{
"username":"john",
"password":"password123"
}
Before saving:
user.setPassword(
passwordEncoder.encode(
request.getPassword()));
Stored in database:
$2a$10$Y67fsK...
At login:
passwordEncoder.matches(
requestPassword,
storedPassword);
This ensures passwords remain protected.
Common Mistakes
Storing Plain Passwords
Wrong:
user.setPassword("password123");
Correct:
user.setPassword(
passwordEncoder.encode(
"password123"));
Using NoOpPasswordEncoder
Avoid:
NoOpPasswordEncoder.getInstance();
It stores passwords in plain text and should only be used for testing.
Encrypting Passwords Instead of Hashing
Passwords should be hashed, not encrypted.
Hashing is one-way and much safer.
Production Best Practices
Use BCryptPasswordEncoder
newBCryptPasswordEncoder();
It provides strong protection and is widely accepted.
Never Log Passwords
Avoid:
System.out.println(password);
Passwords should never appear in logs.
Use Strong Password Policies
Require:
Minimum length
Uppercase letters
Lowercase letters
Numbers
Special characters
Enable Password Rotation
Allow users to reset passwords periodically.
Store Passwords Only in Encoded Form
Never save plain-text passwords in databases.
Summary
In this article, we learned:
Why password encoding is important.
PasswordEncoder interface.
BCryptPasswordEncoder.
Password matching.
Salting and hashing.
DelegatingPasswordEncoder.
Common mistakes.
Production-level best practices.
In Part 4, we will build a Custom UserDetailsService with Database Authentication using Spring Data JPA and MySQL, which is how most real-world applications manage users and credentials.



