Users & Password Encoding
UserDetailsService, storing users, and why passwords must always be salted and hashed with BCrypt.
On this page
Access rules decide what a role may do - but where do users, their passwords, and their roles actually come from? And how do you store a password without creating a disaster waiting to be leaked? This lesson covers both.
Never store passwords as they are
The single most important rule in application security: never store a password in a form you can read back. Not plain text, not reversible encryption. If your database leaks - and databases leak - every account must still be safe.
The answer is a one-way hash: a function that turns a password into a scrambled value it's computationally infeasible to reverse. On login you hash the submitted password and compare hashes.
Encryption is a safe: with the key, you can open it and read what's inside - so whoever holds the key holds the passwords. Hashing is a paper shredder: you can shred a document and later shred another to check if the confetti matches, but you can never un-shred it back into the original. Passwords belong in the shredder, not the safe - there's no legitimate reason to ever read them back.
Use BCrypt (or Argon2)
Not any hash will do. A fast, plain hash (MD5, SHA-256) can be brute-forced billions of times a second and doesn't defend against identical passwords hashing identically. Use a password hashing function built for the job - BCrypt - which is deliberately slow and automatically salts each hash:
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // slow by design, salted per password
}String hash = passwordEncoder.encode(rawPassword); // at registration
boolean ok = passwordEncoder.matches(rawPassword, hash); // at loginThe stored hash includes its own salt, so two users with the same password get different hashes, and precomputed "rainbow table" attacks don't work.
Telling Spring where users live: UserDetailsService
Spring Security loads users through a UserDetailsService - one method that finds a
user by username and returns their stored (hashed) password and authorities:
@Bean
UserDetailsService userDetailsService(MemberRepository members) {
return email -> members.findByEmail(email)
.map(m -> User.withUsername(m.getEmail())
.password(m.getPasswordHash()) // the BCrypt hash from the DB
.roles(m.getRole().name()) // MEMBER or LIBRARIAN
.build())
.orElseThrow(() -> new UsernameNotFoundException(email));
}Spring calls this during authentication, then uses the PasswordEncoder to check the
submitted password against the stored hash. You never compare passwords yourself.
Hash on the way in, once
Encode the password with the PasswordEncoder when you create or change a user, and
store only the hash. The raw password should exist in memory for a moment and never be
logged, returned in a response, or written to the database.
When BookVault registers a member with a chosen password, describe exactly what gets stored in the database and what does not - and explain why using BCrypt instead of a plain SHA-256 hash matters even though both are "hashes."
How should user passwords be stored, and with what?
Key takeaways
- Never store passwords readably: use a one-way hash you can't reverse, not plain text or reversible encryption.
- Use BCrypt (or Argon2): deliberately slow and automatically salted, defeating brute-force and rainbow-table attacks.
- Encode with PasswordEncoder.encode() at registration; verify with matches() at login - never compare passwords yourself.
- A UserDetailsService loads a user (hashed password + authorities) by username for Spring to authenticate.
- The raw password should live only momentarily in memory - never logged, returned, or stored.