Databases, Spring and microservices · 4. Spring Security, lesson 3 of 8

Authentication: UserDetailsService, AuthenticationManager and login

Intermediate3 min read@since 17Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

Authentication answers "who are you?". For username and password, the pieces are:

  • UserDetailsService: your code. loadUserByUsername(email) fetches the user, their password hash and their roles from the database.
  • PasswordEncoder: checks the submitted password against the stored hash (matches()).
  • DaoAuthenticationProvider uses both; AuthenticationManager (usually ProviderManager) asks each provider in turn. Spring Boot wires this up for you once a UserDetailsService and a PasswordEncoder bean exist.
  • On success, an Authentication with the user and their authorities goes into the SecurityContext (and, for session-based login, into the HTTP session).

Ways to log in: form login (classic web apps, session cookie), HTTP Basic (scripts and internal tools), a custom JSON endpoint that calls the AuthenticationManager and returns a token (single-page apps), OAuth 2.0 / OIDC ("Log in with Google"), and since Spring Security 6.4, passkeys and one-time tokens.

Good practice: the same error for a wrong email and a wrong password, account lockout or rate limiting, and a new session ID after login (Spring does this by default to prevent session fixation).

Diagram

Example

Java
@Service
class AppUserDetailsService implements UserDetailsService {
    private final UserRepository users;
    AppUserDetailsService(UserRepository users) { this.users = users; }

    @Override
    public UserDetails loadUserByUsername(String email) {
        AppUser u = users.findByEmail(email.toLowerCase())
                .orElseThrow(() -> new UsernameNotFoundException("No such user"));
        return User.withUsername(u.getEmail())
                .password(u.getPasswordHash())        // the BCrypt hash, never the real password
                .roles(u.getRole())                   // "USER" becomes the authority ROLE_USER
                .accountLocked(u.isLocked())
                .build();
    }
}

@Configuration
class PasswordConfig {
    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();   // stores {bcrypt}$2a$10$…
    }
}
A JSON login endpoint for a single-page app
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
    return config.getAuthenticationManager();             // expose the one Spring Boot built
}

@PostMapping("/auth/login")
TokenResponse login(@RequestBody @Valid LoginRequest req) {
    Authentication auth = authenticationManager.authenticate(
            UsernamePasswordAuthenticationToken.unauthenticated(req.email(), req.password()));
    return tokens.issueFor(auth);       // wrong password: BadCredentialsException, turned into 401
}

Common mistake

Returning "No account with this email" on login or password reset. It tells attackers exactly which emails to target.

Under the hood

By default DaoAuthenticationProvider reports a missing user as BadCredentialsException, so attackers can't tell which emails are registered; keep your own error messages just as vague. It also performs a dummy password check for unknown users so response times don't leak that information either.

Check yourself

Which interface do you implement to load users from your own database?

How this connects

Part of Spring Core and Spring Security in depth.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.