Skip to main content

Week 6 Day 2 — Users, Passwords, and Authentication Flow

Goal

Today I want to understand username/password authentication in Spring Security.

Main questions:

  1. What is a user in Spring Security?
  2. What is UserDetails?
  3. What is UserDetailsService?
  4. What is AuthenticationManager?
  5. What is AuthenticationProvider?
  6. What is DaoAuthenticationProvider?
  7. What is PasswordEncoder?
  8. Why must passwords be hashed?
  9. How does database login work?
  10. How do roles and authorities work?
  11. How do I create a custom UserDetailsService?
  12. What are common exam traps?

1. Quick Review from Week 6 Day 1

In Day 1, I learned:

  • Spring Security protects requests before controllers.
  • Authentication means proving identity.
  • Authorization means checking permissions.
  • 401 means not authenticated.
  • 403 means authenticated but not allowed.
  • Spring Security is filter-chain based.
  • SecurityFilterChain defines security rules.
  • HttpSecurity builds the SecurityFilterChain.
  • SecurityContextHolder stores the current security context.
  • Authentication represents the current authenticated user.
  • PasswordEncoder stores and verifies password hashes.
  • Do not store raw passwords.

Memory sentence:

Authentication = who are you?
Authorization = what can you do?

Today I go deeper into username/password authentication.


2. Big Authentication Flow

When a user logs in with username and password, the simplified flow is:

1. User sends username and password.
2. Spring Security creates an unauthenticated Authentication object.
3. AuthenticationManager receives it.
4. AuthenticationManager delegates to AuthenticationProvider.
5. DaoAuthenticationProvider loads user via UserDetailsService.
6. UserDetailsService loads user from memory or database.
7. PasswordEncoder compares raw password with stored password hash.
8. If valid, authenticated Authentication is created.
9. Authentication is stored in SecurityContext.
10. User is now authenticated for the request.

Short memory version:

Credentials -> AuthenticationManager -> AuthenticationProvider -> UserDetailsService -> PasswordEncoder -> SecurityContext

3. What Is a User in Spring Security?

In my business database, a user may look like this:

@Entity
@Table(name = "users")
public class UserEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String email;

private String passwordHash;

private String role;

private boolean enabled;
}

But Spring Security does not directly require my entity.

Spring Security needs a security representation of the user.

That representation is usually:

UserDetails

Memory sentence:

My database user is my model. UserDetails is Spring Security’s user model.


4. UserDetails

UserDetails represents user information needed for authentication.

It contains:

username
password
authorities
account status flags

Important methods:

String getUsername();

String getPassword();

Collection<? extends GrantedAuthority> getAuthorities();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();

Memory sentence:

UserDetails is the user data Spring Security needs for login.


5. UserDetails Is Not Necessarily My Entity

I should not confuse:

UserEntity
UserDetails

UserEntity is for my database.

UserDetails is for Spring Security authentication.

Example conversion:

UserEntity -> UserDetails

This conversion usually happens inside:

UserDetailsService

Memory sentence:

UserDetailsService converts my stored user into Spring Security user details.


6. Built-In User Class

Spring Security provides a built-in implementation:

org.springframework.security.core.userdetails.User

Example:

UserDetails userDetails = User.builder()
.username("user@example.com")
.password(passwordHash)
.roles("USER")
.build();

This is useful when I do not want to create my own custom UserDetails class.

Memory sentence:

Spring Security’s User is a simple built-in UserDetails implementation.


7. UserDetailsService

UserDetailsService loads a user by username.

Its main method is:

UserDetails loadUserByUsername(String username);

Simple definition:

UserDetailsService is the service Spring Security uses to load user data during username/password authentication.

Example:

@Service
public class DatabaseUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));

return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}

Memory sentence:

UserDetailsService loads the user for authentication.


8. Why the Method Says Username

The method is called:

loadUserByUsername(String username)

But I can use email as username.

Example:

username = email

So this is okay:

loadUserByUsername(String email)

Memory sentence:

In Spring Security, “username” can be an email, login name, or another unique identifier.


9. UsernameNotFoundException

If the user does not exist, throw:

UsernameNotFoundException

Example:

UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));

This tells Spring Security:

No user found with this username.

Memory sentence:

If the login user is not found, throw UsernameNotFoundException.


10. GrantedAuthority

An authority is a permission granted to a user.

Example authorities:

ROLE_USER
ROLE_ADMIN
TASK_READ
TASK_WRITE
INVOICE_APPROVE

In Spring Security, roles are also authorities.

A role usually has prefix:

ROLE_

Memory sentence:

Authorities are permissions. Roles are authorities with ROLE_ prefix.


11. Roles

When I write:

.roles("USER")

Spring Security creates authority:

ROLE_USER

When I write:

.roles("ADMIN")

Spring Security creates:

ROLE_ADMIN

Then this works:

.hasRole("ADMIN")

because it checks for:

ROLE_ADMIN

Memory sentence:

roles("ADMIN") creates ROLE_ADMIN.


12. Authorities

If I want exact permissions, I can use authorities.

Example:

.authorities("TASK_READ", "TASK_WRITE")

Then security rule:

.hasAuthority("TASK_READ")

Roles are broad.

Authorities can be more specific.

Example:

ROLE_ADMIN
TASK_READ
TASK_WRITE
TASK_DELETE
INVOICE_APPROVE

Memory sentence:

Roles are broad groups; authorities can be fine-grained permissions.


13. Role Trap

Wrong:

User.builder()
.username("admin@example.com")
.password(hash)
.roles("ROLE_ADMIN")
.build();

Why wrong?

roles() adds ROLE_ automatically.
This can become ROLE_ROLE_ADMIN.

Correct:

.roles("ADMIN")

or use exact authority:

.authorities("ROLE_ADMIN")

Memory sentence:

With roles(), do not include ROLE_.


14. PasswordEncoder

PasswordEncoder is used to hash and verify passwords.

Example bean:

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

During registration:

String passwordHash = passwordEncoder.encode(rawPassword);

During login:

passwordEncoder.matches(rawPassword, storedPasswordHash);

Memory sentence:

PasswordEncoder hashes raw passwords and verifies login passwords.


15. Never Store Raw Passwords

Bad database row:

email = user@example.com
password = secret123

Good database row:

email = user@example.com
password_hash = $2a$10$...

Why?

If the database is leaked:

raw passwords expose users immediately
hashes reduce damage

Memory sentence:

Store password hashes, never raw passwords.


16. Hashing Is One-Way

Password hashing is one-way.

Meaning:

raw password -> hash
hash -> raw password should not be possible

Login does not decode the password.

Instead:

compare raw password with stored hash using matches()

Example:

boolean ok = passwordEncoder.matches("secret123", storedHash);

Memory sentence:

Passwords are verified, not decoded.


17. BCrypt

A common password encoder is:

BCryptPasswordEncoder

Example:

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

BCrypt is intentionally slow.

Why?

slow hashing makes brute-force attacks harder

Memory sentence:

Password hashing should be slow enough to resist brute-force attacks.


18. NoOpPasswordEncoder Warning

You may see examples with:

NoOpPasswordEncoder

or passwords like:

{noop}password

This means:

no password hashing
plain text password

Use only for tiny demos, never real apps.

Memory sentence:

noop means no hashing. Do not use it in real applications.


19. Authentication

Authentication represents authentication information.

Before authentication:

principal = username
credentials = raw password
authenticated = false

After authentication:

principal = UserDetails
credentials = usually cleared
authorities = roles/permissions
authenticated = true

Memory sentence:

Authentication can represent both the login attempt and the authenticated user.


20. UsernamePasswordAuthenticationToken

For username/password login, Spring Security often uses:

UsernamePasswordAuthenticationToken

Before authentication:

new UsernamePasswordAuthenticationToken(email, rawPassword)

After authentication:

principal = UserDetails
authorities = loaded roles
authenticated = true

Memory sentence:

UsernamePasswordAuthenticationToken carries username/password authentication data.


21. AuthenticationManager

AuthenticationManager is responsible for authenticating.

Simple definition:

AuthenticationManager receives an authentication request and returns an authenticated result or throws an exception.

Example mental model:

AuthenticationManager.authenticate(authentication)

If success:

returns authenticated Authentication

If failure:

throws AuthenticationException

Memory sentence:

AuthenticationManager decides whether authentication succeeds.


22. AuthenticationProvider

AuthenticationProvider performs a specific authentication strategy.

Examples:

username/password
JWT token
LDAP
OAuth2
API key

For username/password, the common provider is:

DaoAuthenticationProvider

Memory sentence:

AuthenticationProvider knows how to authenticate one kind of credential.


23. DaoAuthenticationProvider

DaoAuthenticationProvider authenticates username/password using:

UserDetailsService
PasswordEncoder

Flow:

1. Receive username/password authentication token.
2. Use UserDetailsService to load user by username.
3. Read stored password hash from UserDetails.
4. Use PasswordEncoder to compare raw password with stored hash.
5. If valid, return authenticated Authentication.
6. If invalid, throw authentication exception.

Memory sentence:

DaoAuthenticationProvider uses UserDetailsService plus PasswordEncoder.


24. Full Username/Password Authentication Flow

Detailed flow:

1. User submits email and password.
2. Authentication filter reads credentials.
3. Filter creates UsernamePasswordAuthenticationToken.
4. AuthenticationManager receives the token.
5. ProviderManager delegates to DaoAuthenticationProvider.
6. DaoAuthenticationProvider calls UserDetailsService.
7. UserDetailsService loads user from database.
8. DaoAuthenticationProvider checks password with PasswordEncoder.
9. If password is valid, authenticated Authentication is returned.
10. SecurityContextHolder stores Authentication.
11. Authorization rules can now check roles/authorities.
12. Request can continue.

Short memory:

Filter -> AuthenticationManager -> DaoAuthenticationProvider -> UserDetailsService -> PasswordEncoder -> SecurityContext

25. Database User Entity

Example entity:

@Entity
@Table(name = "users")
public class UserEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String email;

@Column(name = "password_hash", nullable = false)
private String passwordHash;

@Column(nullable = false)
private String role;

@Column(nullable = false)
private boolean enabled = true;

protected UserEntity() {
}

public UserEntity(String email, String passwordHash, String role) {
this.email = email;
this.passwordHash = passwordHash;
this.role = role;
}

public Long getId() {
return id;
}

public String getEmail() {
return email;
}

public String getPasswordHash() {
return passwordHash;
}

public String getRole() {
return role;
}

public boolean isEnabled() {
return enabled;
}
}

Important:

Database stores password hash, not raw password.

26. User Repository

public interface UserRepository extends JpaRepository<UserEntity, Long> {

Optional<UserEntity> findByEmail(String email);

boolean existsByEmail(String email);
}

Used for:

load user during login
check duplicate email during registration

Memory sentence:

User repository loads users from the database.


27. Database UserDetailsService

@Service
public class DatabaseUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));

return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}

If database role is:

USER

then:

.roles(user.getRole())

creates authority:

ROLE_USER

Important:

Store role as USER or ADMIN if using roles().
Do not store ROLE_USER unless using authorities().

28. Security Config with Database Users

@Configuration
public class SecurityConfig {

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.build();
}

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

Because I have a UserDetailsService bean, Spring Security can use it for username/password authentication.

Memory sentence:

Define UserDetailsService and PasswordEncoder for database-backed username/password login.


29. Registration Flow

Registration means creating a new user.

Request DTO:

public record RegisterRequest(

@NotBlank
@Email
String email,

@NotBlank
@Size(min = 8)
String password
) {
}

Service:

@Service
public class AuthService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

public AuthService(
UserRepository userRepository,
PasswordEncoder passwordEncoder
) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}

@Transactional
public void register(RegisterRequest request) {
if (userRepository.existsByEmail(request.email())) {
throw new EmailAlreadyUsedException(request.email());
}

String passwordHash = passwordEncoder.encode(request.password());

UserEntity user = new UserEntity(
request.email(),
passwordHash,
"USER"
);

userRepository.save(user);
}
}

Memory sentence:

During registration, encode the raw password before saving.


30. Login Flow with HTTP Basic

With HTTP Basic, I do not normally write a login controller.

Client sends:

Authorization: Basic base64(email:password)

Spring Security:

extracts credentials
loads user
checks password
sets SecurityContext
authorizes request

This is good for learning and simple internal APIs.

For public REST APIs, JWT or OAuth2 is more common.

Memory sentence:

HTTP Basic login is handled by Spring Security filters, not my controller.


31. Login Flow with Custom Login Endpoint

Sometimes in REST APIs, I create:

POST /api/auth/login

The endpoint receives email and password, authenticates them, and returns a token.

Request:

public record LoginRequest(

@NotBlank
@Email
String email,

@NotBlank
String password
) {
}

Controller:

@RestController
@RequestMapping("/api/auth")
public class AuthController {

private final AuthenticationManager authenticationManager;

public AuthController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@PostMapping("/login")
public LoginResponse login(@Valid @RequestBody LoginRequest request) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.email(),
request.password()
)
);

return new LoginResponse(
authentication.getName(),
"token-will-come-later"
);
}
}

Response:

public record LoginResponse(
String username,
String token
) {
}

Important:

This checks username/password using Spring Security.
Later, JWT lesson will replace token-will-come-later with real JWT.

32. Exposing AuthenticationManager

In many modern configurations, I can expose AuthenticationManager like this:

@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration
) throws Exception {
return configuration.getAuthenticationManager();
}

Full config part:

@Configuration
public class SecurityConfig {

@Bean
AuthenticationManager authenticationManager(
AuthenticationConfiguration configuration
) throws Exception {
return configuration.getAuthenticationManager();
}

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

Memory sentence:

A custom login endpoint can use AuthenticationManager to authenticate credentials.


33. What Happens on Bad Password?

User enters wrong password.

Flow:

1. UserDetailsService loads user.
2. PasswordEncoder.matches(raw, hash) returns false.
3. Authentication fails.
4. Spring Security throws authentication exception.
5. Response is usually 401.

Memory sentence:

Wrong password means authentication failure, usually 401.


34. Disabled User

If the user is disabled:

.disabled(!user.isEnabled())

then Spring Security can reject login.

Database:

enabled = false

Result:

authentication fails

Memory sentence:

UserDetails account flags can prevent login.


35. Account Status Flags

UserDetails supports:

enabled
account non-expired
account non-locked
credentials non-expired

Meaning:

FlagMeaning
enableduser is active
account non-expiredaccount is not expired
account non-lockedaccount is not locked
credentials non-expiredpassword is not expired

For many simple apps, only enabled is used first.

Memory sentence:

User account flags can block authentication.


36. Custom UserDetails Class

Sometimes I need user ID in the principal.

Create custom principal:

public class AppUserPrincipal implements UserDetails {

private final Long id;
private final String email;
private final String passwordHash;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean enabled;

public AppUserPrincipal(
Long id,
String email,
String passwordHash,
Collection<? extends GrantedAuthority> authorities,
boolean enabled
) {
this.id = id;
this.email = email;
this.passwordHash = passwordHash;
this.authorities = authorities;
this.enabled = enabled;
}

public Long getId() {
return id;
}

@Override
public String getUsername() {
return email;
}

@Override
public String getPassword() {
return passwordHash;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public boolean isEnabled() {
return enabled;
}
}

Then:

return new AppUserPrincipal(
user.getId(),
user.getEmail(),
user.getPasswordHash(),
List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole())),
user.isEnabled()
);

Memory sentence:

Custom UserDetails is useful when I need extra principal data like user ID.


37. Getting Custom Principal in Controller

If principal is custom:

@GetMapping("/api/me")
public MeDto me(@AuthenticationPrincipal AppUserPrincipal principal) {
return new MeDto(
principal.getId(),
principal.getUsername()
);
}

DTO:

public record MeDto(
Long id,
String email
) {
}

Memory sentence:

@AuthenticationPrincipal injects the current principal.


38. Principal vs Authentication vs @AuthenticationPrincipal

Controller options:

@GetMapping("/api/me1")
public String me1(Principal principal) {
return principal.getName();
}
@GetMapping("/api/me2")
public String me2(Authentication authentication) {
return authentication.getName();
}
@GetMapping("/api/me3")
public MeDto me3(@AuthenticationPrincipal AppUserPrincipal principal) {
return new MeDto(principal.getId(), principal.getUsername());
}

Use:

Principal for simple username
Authentication for authorities/details
@AuthenticationPrincipal for custom principal data

39. SecurityContext in Service

Sometimes service needs current user.

Simple option:

Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();

String email = authentication.getName();

But do not spread this everywhere.

Better design:

controller reads current principal
passes user ID/email to service
or use a small CurrentUserService abstraction

Example:

@Service
public class CurrentUserService {

public String currentUsername() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();

return authentication.getName();
}
}

Memory sentence:

Avoid random SecurityContextHolder calls everywhere; centralize current-user access.


40. Security + Database Flow Example

Endpoint:

GET /api/tasks
Authorization: Basic ...

Flow:

1. BasicAuthenticationFilter reads Authorization header.
2. It creates username/password authentication request.
3. AuthenticationManager authenticates.
4. DaoAuthenticationProvider loads user with UserDetailsService.
5. UserRepository finds user by email.
6. PasswordEncoder checks password.
7. Authentication is stored in SecurityContext.
8. Authorization checks endpoint rules.
9. Controller runs.
10. Controller/service can read current user.

Memory sentence:

Login uses the user repository indirectly through UserDetailsService.


41. Bad Practice: Returning Password Hash

Bad DTO:

public record UserDto(
Long id,
String email,
String passwordHash
) {
}

Never expose password hash in API responses.

Better:

public record UserDto(
Long id,
String email,
String role
) {
}

Memory sentence:

Do not return password hashes to clients.


42. Bad Practice: Logging Passwords

Bad:

log.info("Login request: {}", request);

If request contains password, it may log raw password.

Better:

log.info("Login attempt for email={}", request.email());

Memory sentence:

Never log raw passwords.


43. Bad Practice: Registering User Without Encoding Password

Bad:

UserEntity user = new UserEntity(
request.email(),
request.password(),
"USER"
);

This stores raw password.

Correct:

String passwordHash = passwordEncoder.encode(request.password());

UserEntity user = new UserEntity(
request.email(),
passwordHash,
"USER"
);

Memory sentence:

Always encode password before saving.


44. Bad Practice: Manual Password Check in Controller

Bad:

@PostMapping("/login")
public String login(@RequestBody LoginRequest request) {
UserEntity user = userRepository.findByEmail(request.email()).orElseThrow();

if (!user.getPasswordHash().equals(request.password())) {
throw new RuntimeException("Bad password");
}

return "ok";
}

Problems:

compares raw password incorrectly
bypasses PasswordEncoder
bypasses Spring Security authentication flow
bad error handling
unsafe

Correct:

Use AuthenticationManager or Spring Security filters.

Memory sentence:

Do not manually compare passwords in controllers.


45. Common Exam Traps

Trap 1

UserDetailsService loads user data; it does not check passwords by itself.


Trap 2

DaoAuthenticationProvider uses UserDetailsService and PasswordEncoder.


Trap 3

PasswordEncoder is one-way. It does not decode passwords.


Trap 4

Store password hashes, not raw passwords.


Trap 5

roles("ADMIN") creates ROLE_ADMIN.


Trap 6

Do not use roles("ROLE_ADMIN").


Trap 7

hasRole("ADMIN") checks for ROLE_ADMIN.


Trap 8

hasAuthority("ROLE_ADMIN") checks exactly ROLE_ADMIN.


Trap 9

UsernameNotFoundException is used when user cannot be loaded.


Trap 10

Wrong password usually causes authentication failure and 401.


Trap 11

User database entity and UserDetails are not the same thing.


Trap 12

AuthenticationManager authenticates credentials.


Trap 13

AuthenticationProvider performs a specific authentication strategy.


Trap 14

Custom login endpoints can use AuthenticationManager.


Trap 15

Never return or log password hashes or raw passwords.


46. Real Exam Question: UserDetails

Question:

What is UserDetails?

Answer:

UserDetails is Spring Security’s representation of user information needed for authentication, such as username, password, authorities, and account status flags.


47. Real Exam Question: UserDetailsService

Question:

What does UserDetailsService do?

Answer:

UserDetailsService loads a user by username and returns a UserDetails object for authentication.


48. Real Exam Question: PasswordEncoder

Question:

What is PasswordEncoder used for?

Answer:

PasswordEncoder hashes raw passwords and verifies raw login passwords against stored password hashes.


49. Real Exam Question: AuthenticationManager

Question:

What does AuthenticationManager do?

Answer:

AuthenticationManager receives an authentication request and returns an authenticated Authentication if credentials are valid, or throws an authentication exception if they are invalid.


50. Real Exam Question: DaoAuthenticationProvider

Question:

What does DaoAuthenticationProvider use?

Answer:

It uses UserDetailsService to load the user and PasswordEncoder to verify the password.


51. Real Exam Question: Role Prefix

Question:

What does roles("ADMIN") create?

Answer:

It creates the authority ROLE_ADMIN.


52. Real Exam Question: hasRole

Question:

What does hasRole("ADMIN") check?

Answer:

It checks whether the user has authority ROLE_ADMIN.


53. Real Exam Question: Password Hashing

Question:

Should passwords be stored raw or hashed?

Answer:

Passwords should be stored as hashes, never as raw text.


54. Real Exam Question: @AuthenticationPrincipal

Question:

What does @AuthenticationPrincipal do?

Answer:

It injects the current authenticated principal into a controller method parameter.


55. Interview Answer

Question:

Explain the username/password authentication flow in Spring Security.

Good answer:

When a user submits username and password, Spring Security creates an authentication request, usually a UsernamePasswordAuthenticationToken. The AuthenticationManager receives it and delegates to an AuthenticationProvider, commonly DaoAuthenticationProvider. The provider uses UserDetailsService to load the user and PasswordEncoder to compare the raw password with the stored password hash. If authentication succeeds, Spring Security creates an authenticated Authentication and stores it in the SecurityContext.


56. Interview Answer

Question:

What is the role of UserDetailsService?

Good answer:

UserDetailsService loads user information by username. In a database-backed application, it usually queries the user repository, finds the user by email or username, and converts the database user into a UserDetails object with username, password hash, roles, authorities, and account status flags.


57. Interview Answer

Question:

Why do we need PasswordEncoder?

Good answer:

We need PasswordEncoder because passwords should never be stored as raw text. A password encoder hashes the password before saving and later verifies a raw login password against the stored hash. It is one-way, so it does not decode passwords. This reduces risk if the database is leaked.


58. Interview Answer

Question:

What is the difference between role and authority?

Good answer:

An authority is a permission in Spring Security. A role is a special kind of authority that usually has the ROLE_ prefix. For example, roles("ADMIN") creates the authority ROLE_ADMIN, and hasRole("ADMIN") checks for ROLE_ADMIN. For fine-grained permissions, I can use authorities such as TASK_READ or INVOICE_APPROVE.


59. Interview Answer

Question:

How would you implement database login?

Good answer:

I would store users in a database with a unique email, password hash, role or authorities, and enabled flag. I would implement UserDetailsService to load the user from the repository and return UserDetails. I would define a PasswordEncoder, usually BCrypt. Then Spring Security’s DaoAuthenticationProvider can use the UserDetailsService and PasswordEncoder to authenticate username/password login.


60. Tiny Code Practice

Create repository:

public interface UserRepository extends JpaRepository<UserEntity, Long> {

Optional<UserEntity> findByEmail(String email);
}

Create UserDetailsService:

@Service
public class DatabaseUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsername(String email) {
UserEntity user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));

return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles(user.getRole())
.disabled(!user.isEnabled())
.build();
}
}

Questions:

  1. What does findByEmail return?
  2. What happens if user is not found?
  3. What does .password(...) expect?
  4. What does .roles("USER") create?
  5. What does .disabled(!user.isEnabled()) do?

Answers:

  1. Optional<UserEntity>.
  2. UsernameNotFoundException is thrown.
  3. The stored encoded password hash.
  4. Authority ROLE_USER.
  5. It disables login if the user is not enabled.

61. Tiny Bug Practice 1

Problem:

return User.builder()
.username(user.getEmail())
.password(user.getPasswordHash())
.roles("ROLE_ADMIN")
.build();

Question:

What is wrong?

Answer:

roles() adds the ROLE_ prefix automatically. This can create ROLE_ROLE_ADMIN.

Correct:

.roles("ADMIN")

or:

.authorities("ROLE_ADMIN")

62. Tiny Bug Practice 2

Problem:

String passwordHash = request.password();
userRepository.save(new UserEntity(request.email(), passwordHash, "USER"));

Question:

What is wrong?

Answer:

The raw password is saved directly. It must be encoded first.

Correct:

String passwordHash = passwordEncoder.encode(request.password());
userRepository.save(new UserEntity(request.email(), passwordHash, "USER"));

63. Tiny Bug Practice 3

Problem:

if (user.getPasswordHash().equals(request.password())) {
return "login ok";
}

Question:

What is wrong?

Answer:

This compares a stored hash with a raw password and bypasses PasswordEncoder. Use passwordEncoder.matches(rawPassword, storedHash) or better use AuthenticationManager.


Practice Questions and Answers

Question 1

What is UserDetails?

Answer:

UserDetails is Spring Security’s representation of user data needed for authentication, including username, password, authorities, and account status flags.


Question 2

What is UserDetailsService?

Answer:

UserDetailsService loads user data by username and returns UserDetails.


Question 3

What does loadUserByUsername do?

Answer:

It finds a user by username and converts that user into UserDetails.


Question 4

Can username be an email?

Answer:

Yes. In many apps, the email is used as the username.


Question 5

What exception should be thrown when the user is not found?

Answer:

UsernameNotFoundException.


Question 6

What is PasswordEncoder?

Answer:

PasswordEncoder hashes raw passwords and verifies raw passwords against stored hashes.


Question 7

Why should passwords be hashed?

Answer:

Because raw passwords are dangerous if leaked. Hashing reduces damage and prevents storing the actual password.


Question 8

Does PasswordEncoder decode passwords?

Answer:

No. Password encoding is one-way. It verifies with matches.


Question 9

What is AuthenticationManager?

Answer:

AuthenticationManager authenticates an authentication request and returns an authenticated result or throws an exception.


Question 10

What is AuthenticationProvider?

Answer:

AuthenticationProvider performs a specific authentication strategy.


Question 11

What is DaoAuthenticationProvider?

Answer:

DaoAuthenticationProvider is the common username/password provider that uses UserDetailsService and PasswordEncoder.


Question 12

What does DaoAuthenticationProvider use?

Answer:

It uses UserDetailsService to load the user and PasswordEncoder to verify the password.


Question 13

What does roles("ADMIN") create?

Answer:

It creates authority ROLE_ADMIN.


Question 14

What does hasRole("ADMIN") check?

Answer:

It checks for authority ROLE_ADMIN.


Question 15

What is the difference between role and authority?

Answer:

An authority is a permission. A role is usually an authority with the ROLE_ prefix.


Question 16

What is UsernamePasswordAuthenticationToken?

Answer:

It is an Authentication implementation commonly used to carry username/password authentication data.


Question 17

What happens when the password is wrong?

Answer:

Authentication fails, and the response is usually 401.


Question 18

What is @AuthenticationPrincipal?

Answer:

@AuthenticationPrincipal injects the current authenticated principal into a controller method.


Question 19

Why should I not log login request objects directly?

Answer:

Because the object may contain raw passwords or sensitive data.


Question 20

What is the full database login flow?

Answer:

The user sends username/password. Spring Security creates an authentication request. AuthenticationManager delegates to DaoAuthenticationProvider. The provider uses UserDetailsService to load the user from the database and PasswordEncoder to verify the password. If valid, an authenticated Authentication is stored in the SecurityContext.

Final Memory Sentences

  • UserDetails is Spring Security’s user representation.
  • UserDetailsService loads a user by username.
  • Username can be an email.
  • Throw UsernameNotFoundException if the user is missing.
  • PasswordEncoder hashes and verifies passwords.
  • Password hashing is one-way.
  • Store password hashes, never raw passwords.
  • BCrypt is a common password encoder.
  • AuthenticationManager authenticates credentials.
  • AuthenticationProvider performs a specific authentication strategy.
  • DaoAuthenticationProvider uses UserDetailsService and PasswordEncoder.
  • Authentication represents the login attempt or authenticated user.
  • UsernamePasswordAuthenticationToken carries username/password data.
  • roles("ADMIN") creates ROLE_ADMIN.
  • hasRole("ADMIN") checks ROLE_ADMIN.
  • Do not use roles("ROLE_ADMIN").
  • Use @AuthenticationPrincipal to inject the current principal.
  • Never return password hashes.
  • Never log raw passwords.
  • Encode passwords during registration.
  • Use AuthenticationManager for custom login endpoints.