or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aot-native-support.mdauthentication-core.mdauthentication-events.mdauthentication-management.mdauthentication-tokens.mdauthorities.mdauthorization.mdcompromised-password.mdconcurrent-async.mddao-authentication.mdexpression-access-control.mdindex.mdjaas-authentication.mdjackson-serialization.mdmethod-security.mdobservation-metrics.mdone-time-tokens.mdprovisioning.mdsecurity-context.mdsession-management.mduser-details.md
tile.json

dao-authentication.mddocs/

DAO Authentication

DAO (Data Access Object) authentication provides a flexible authentication mechanism using UserDetailsService to load user information from various data sources. This pattern decouples authentication logic from user data storage.

Key Information for Agents

Core Capabilities:

  • DaoAuthenticationProvider uses UserDetailsService to load users and PasswordEncoder to verify credentials
  • Extends AbstractUserDetailsAuthenticationProvider with user retrieval and password checking
  • Supports user caching via UserCache to reduce database lookups
  • Supports password updates via UserDetailsPasswordService (for encoding migration)
  • Supports compromised password checking via CompromisedPasswordChecker
  • Account status checking via UserDetailsChecker (pre and post authentication)
  • Reactive support via UserDetailsRepositoryReactiveAuthenticationManager

Key Interfaces and Classes:

  • AbstractUserDetailsAuthenticationProvider - Abstract base with authentication flow
  • DaoAuthenticationProvider - Concrete implementation using UserDetailsService and PasswordEncoder
  • UserDetailsChecker - Interface: check(UserDetails) throws AuthenticationException
  • AccountStatusUserDetailsChecker - Default implementation checking standard flags
  • UserDetailsPasswordService - Interface: updatePassword(UserDetails, String) returns UserDetails
  • CachingUserDetailsService - Decorator adding caching to any UserDetailsService
  • UserCache - Caching interface (implementations: NullUserCache, SpringCacheBasedUserCache)
  • AbstractUserDetailsReactiveAuthenticationManager - Abstract reactive base
  • UserDetailsRepositoryReactiveAuthenticationManager - Reactive implementation

Default Behaviors:

  • Authentication flow: 1) Retrieve user, 2) Pre-authentication checks, 3) Password verification, 4) Post-authentication checks
  • Credentials erased after authentication (default: true, configurable via setEraseCredentialsAfterAuthentication())
  • User retrieved from cache if available, otherwise from UserDetailsService
  • Password checked via PasswordEncoder.matches(rawPassword, encodedPassword)
  • Account status checked: enabled, account non-expired, account non-locked, credentials non-expired
  • Compromised password check: Runs after successful password verification (if configured)
  • Password migration: UserDetailsPasswordService called automatically when encoding changes

Threading Model:

  • Synchronous provider executes in calling thread
  • Thread-safe: Stateless provider, UserDetailsService should be thread-safe
  • Reactive manager works with Mono<UserDetails>

Lifecycle:

  • UserDetailsService required in constructor (cannot be changed after construction)
  • PasswordEncoder set via setPasswordEncoder() (optional, delegates to database if not set)
  • UserCache set via setUserCache() (default: NullUserCache - no caching)
  • UserDetailsPasswordService set via setUserDetailsPasswordService() (optional)
  • CompromisedPasswordChecker set via setCompromisedPasswordChecker() (optional)

Exceptions:

  • UsernameNotFoundException - User not found (may be hidden if hideUserNotFoundExceptions is true)
  • BadCredentialsException - Password doesn't match
  • DisabledException - Account disabled (!user.isEnabled())
  • AccountExpiredException - Account expired (!user.isAccountNonExpired())
  • LockedException - Account locked (!user.isAccountNonLocked())
  • CredentialsExpiredException - Credentials expired (!user.isCredentialsNonExpired())
  • CompromisedPasswordException - Password found in breach database (if checker configured)

Edge Cases:

  • Null password: If user has null password, authentication may fail (depends on PasswordEncoder)
  • Empty authorities: User with no authorities is valid (authorization may deny access)
  • User not found: Exception may be hidden (converted to BadCredentialsException) if hideUserNotFoundExceptions is true
  • Password encoding migration: UserDetailsPasswordService called automatically when password encoding changes
  • Caching: Cache hit avoids database lookup, cache miss loads from service and stores in cache
  • Compromised password: Check runs after password verification, throws exception if compromised
  • Reactive errors: Reactive manager emits error Mono on authentication failure

Core Authentication Provider

AbstractUserDetailsAuthenticationProvider

Abstract base class for authentication providers that rely on retrieving UserDetails.

package org.springframework.security.authentication.dao;

abstract class AbstractUserDetailsAuthenticationProvider
    implements AuthenticationProvider, InitializingBean {
    AbstractUserDetailsAuthenticationProvider();

    Authentication authenticate(Authentication authentication)
        throws AuthenticationException;

    protected abstract UserDetails retrieveUser(
        String username,
        UsernamePasswordAuthenticationToken authentication
    ) throws AuthenticationException;

    protected abstract void additionalAuthenticationChecks(
        UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication
    ) throws AuthenticationException;

    void setUserDetailsChecker(UserDetailsChecker userDetailsChecker);
    void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks);
    void setUserCache(UserCache userCache);
    void setForcePrincipalAsString(boolean forcePrincipalAsString);
    void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions);
    void setEraseCredentialsAfterAuthentication(boolean eraseCredentialsAfterAuthentication);
}

Key Methods:

Authentication authenticate(Authentication authentication)
    throws AuthenticationException
  • Performs the authentication process, delegating to abstract methods for user retrieval and credential checking.
protected abstract UserDetails retrieveUser(
    String username,
    UsernamePasswordAuthenticationToken authentication
) throws AuthenticationException
  • Abstract method to retrieve user details. Implementations must fetch the user from their data source.
protected abstract void additionalAuthenticationChecks(
    UserDetails userDetails,
    UsernamePasswordAuthenticationToken authentication
) throws AuthenticationException
  • Abstract method to perform additional checks (like password verification) on the retrieved user.
void setUserDetailsChecker(UserDetailsChecker userDetailsChecker)
  • Sets the pre-authentication checker for validating user account status (enabled, locked, expired).
void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks)
  • Sets the post-authentication checker for validating credentials expiration.
void setUserCache(UserCache userCache)
  • Sets a cache for storing and retrieving UserDetails to improve performance.
void setForcePrincipalAsString(boolean forcePrincipalAsString)
  • If true, the username string is used as the principal instead of the UserDetails object.
void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions)
  • If true, UsernameNotFoundException is converted to BadCredentialsException (prevents user enumeration).
void setEraseCredentialsAfterAuthentication(boolean eraseCredentialsAfterAuthentication)
  • If true, credentials are erased from the authentication token after successful authentication (default: true).

Authentication Flow:

  1. Extract username from authentication token
  2. Retrieve user from cache or call retrieveUser()
  3. Run pre-authentication checks (account status)
  4. Perform additionalAuthenticationChecks() (password verification)
  5. Run post-authentication checks (credentials expiration)
  6. Return successful Authentication with authorities

DaoAuthenticationProvider

Concrete implementation using UserDetailsService for user retrieval and PasswordEncoder for credential verification.

package org.springframework.security.authentication.dao;

class DaoAuthenticationProvider
    extends AbstractUserDetailsAuthenticationProvider {
    DaoAuthenticationProvider();

    void setUserDetailsService(UserDetailsService userDetailsService);
    void setPasswordEncoder(PasswordEncoder passwordEncoder);
    void setUserDetailsPasswordService(
        UserDetailsPasswordService userDetailsPasswordService);
    void setCompromisedPasswordChecker(
        CompromisedPasswordChecker compromisedPasswordChecker);

    protected final UserDetails retrieveUser(
        String username,
        UsernamePasswordAuthenticationToken authentication
    ) throws AuthenticationException;

    protected void additionalAuthenticationChecks(
        UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication
    ) throws AuthenticationException;
}

Key Methods:

void setUserDetailsService(UserDetailsService userDetailsService)
  • Sets the service used to retrieve user details by username.
void setPasswordEncoder(PasswordEncoder passwordEncoder)
  • Sets the password encoder for verifying credentials.
void setUserDetailsPasswordService(
    UserDetailsPasswordService userDetailsPasswordService)
  • Sets the service for updating passwords (e.g., for password encoding migration).
void setCompromisedPasswordChecker(
    CompromisedPasswordChecker compromisedPasswordChecker)
  • Sets the checker for detecting compromised passwords.

Configuration Example:

@Configuration
public class AuthenticationConfiguration {

    @Bean
    public DaoAuthenticationProvider authenticationProvider(
        UserDetailsService userDetailsService,
        PasswordEncoder passwordEncoder
    ) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder);
        provider.setUserDetailsPasswordService(userDetailsPasswordService());
        provider.setCompromisedPasswordChecker(compromisedPasswordChecker());
        provider.setUserCache(userCache());
        provider.setHideUserNotFoundExceptions(true);
        return provider;
    }

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

    @Bean
    public UserDetailsPasswordService userDetailsPasswordService() {
        return new CustomUserDetailsPasswordService();
    }

    @Bean
    public CompromisedPasswordChecker compromisedPasswordChecker() {
        return new HaveIBeenPwnedRestApiPasswordChecker();
    }

    @Bean
    public UserCache userCache() {
        return new SpringCacheBasedUserCache(
            cacheManager().getCache("userCache")
        );
    }
}

Complete Example:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException {

        return userRepository.findByUsername(username)
            .map(this::convertToUserDetails)
            .orElseThrow(() ->
                new UsernameNotFoundException("User not found: " + username));
    }

    private UserDetails convertToUserDetails(UserEntity entity) {
        return User.builder()
            .username(entity.getUsername())
            .password(entity.getPassword())
            .authorities(entity.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList()))
            .accountExpired(!entity.isAccountNonExpired())
            .accountLocked(!entity.isAccountNonLocked())
            .credentialsExpired(!entity.isCredentialsNonExpired())
            .disabled(!entity.isEnabled())
            .build();
    }
}

@Configuration
public class SecurityConfig {

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider(
        CustomUserDetailsService userDetailsService
    ) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());

        // Optional: Enable user caching
        provider.setUserCache(userCache());

        // Optional: Enable compromised password checking
        provider.setCompromisedPasswordChecker(compromisedPasswordChecker());

        // Optional: Hide user not found exceptions (security)
        provider.setHideUserNotFoundExceptions(true);

        // Optional: Custom account status checker
        provider.setUserDetailsChecker(customUserDetailsChecker());

        return provider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserCache userCache() {
        return new SpringCacheBasedUserCache(
            cacheManager().getCache("userCache")
        );
    }

    @Bean
    public CompromisedPasswordChecker compromisedPasswordChecker() {
        return new HaveIBeenPwnedRestApiPasswordChecker();
    }

    @Bean
    public UserDetailsChecker customUserDetailsChecker() {
        return new AccountStatusUserDetailsChecker();
    }

    @Bean
    public AuthenticationManager authenticationManager(
        DaoAuthenticationProvider authenticationProvider
    ) {
        return new ProviderManager(authenticationProvider);
    }
}

User Details Checking

UserDetailsChecker

Interface for checking the validity of a UserDetails object.

package org.springframework.security.core.userdetails;

interface UserDetailsChecker {
    void check(UserDetails toCheck) throws AuthenticationException;
}

Key Methods:

void check(UserDetails toCheck) throws AuthenticationException
  • Checks the user details and throws an exception if the account is invalid.

AccountStatusUserDetailsChecker

Default implementation that checks account status flags.

package org.springframework.security.core.userdetails;

class AccountStatusUserDetailsChecker implements UserDetailsChecker {
    AccountStatusUserDetailsChecker();

    void check(UserDetails user) throws AuthenticationException;
}

Checks Performed:

  • isAccountNonLocked() - Throws LockedException if false
  • isEnabled() - Throws DisabledException if false
  • isAccountNonExpired() - Throws AccountExpiredException if false

Usage:

DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsChecker(new AccountStatusUserDetailsChecker());

Custom Checker Example:

public class CustomUserDetailsChecker implements UserDetailsChecker {

    @Override
    public void check(UserDetails user) {
        if (!user.isAccountNonLocked()) {
            throw new LockedException("Account is locked");
        }

        if (!user.isEnabled()) {
            throw new DisabledException("Account is disabled");
        }

        if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException("Account has expired");
        }

        // Custom checks
        if (user instanceof CustomUserDetails customUser) {
            if (customUser.requiresPasswordChange()) {
                throw new CredentialsExpiredException("Password change required");
            }

            if (!customUser.hasAcceptedTerms()) {
                throw new CustomAuthenticationException("Terms not accepted");
            }
        }
    }
}

User Caching

CachingUserDetailsService

Decorator that adds caching to any UserDetailsService implementation.

package org.springframework.security.core.userdetails.cache;

class CachingUserDetailsService implements UserDetailsService {
    CachingUserDetailsService(UserDetailsService delegate);

    void setUserCache(UserCache userCache);
    UserDetails loadUserByUsername(String username)
        throws UsernameNotFoundException;
}

Constructor:

CachingUserDetailsService(UserDetailsService delegate)

Configuration Example:

@Configuration
public class CachingConfiguration {

    @Bean
    public UserDetailsService cachedUserDetailsService(
        UserDetailsService delegate,
        UserCache userCache
    ) {
        CachingUserDetailsService cachingService =
            new CachingUserDetailsService(delegate);
        cachingService.setUserCache(userCache);
        return cachingService;
    }

    @Bean
    public UserCache userCache(CacheManager cacheManager) {
        return new SpringCacheBasedUserCache(
            cacheManager.getCache("users")
        );
    }

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users");
    }
}

Password Update Support

UserDetailsPasswordService

Interface for updating user passwords after authentication.

package org.springframework.security.core.userdetails;

interface UserDetailsPasswordService {
    UserDetails updatePassword(UserDetails user, String newPassword);
}

Key Methods:

UserDetails updatePassword(UserDetails user, String newPassword)
  • Updates the password for the user and returns the updated UserDetails.

Implementation Example:

@Service
public class JpaUserDetailsPasswordService
    implements UserDetailsPasswordService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        UserEntity entity = userRepository.findByUsername(user.getUsername())
            .orElseThrow(() -> new UsernameNotFoundException(
                "User not found: " + user.getUsername()));

        entity.setPassword(newPassword);
        UserEntity saved = userRepository.save(entity);

        return User.builder()
            .username(saved.getUsername())
            .password(saved.getPassword())
            .authorities(user.getAuthorities())
            .accountExpired(!saved.isAccountNonExpired())
            .accountLocked(!saved.isAccountNonLocked())
            .credentialsExpired(!saved.isCredentialsNonExpired())
            .disabled(!saved.isEnabled())
            .build();
    }
}

// Configuration
@Bean
public DaoAuthenticationProvider authenticationProvider(
    UserDetailsService userDetailsService,
    UserDetailsPasswordService passwordService
) {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder());
    provider.setUserDetailsPasswordService(passwordService);
    return provider;
}

Password Encoding Migration:

@Service
public class MigratingPasswordService implements UserDetailsPasswordService {

    private final UserRepository userRepository;

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        // Called automatically when user logs in with old password encoding
        // newPassword is already encoded with the new encoder

        UserEntity entity = userRepository.findByUsername(user.getUsername())
            .orElseThrow();

        entity.setPassword(newPassword);
        entity.setPasswordMigrated(true);
        userRepository.save(entity);

        return User.builder()
            .username(user.getUsername())
            .password(newPassword)
            .authorities(user.getAuthorities())
            .build();
    }
}

Reactive DAO Authentication

AbstractUserDetailsReactiveAuthenticationManager

Abstract base for reactive authentication using UserDetails.

package org.springframework.security.authentication;

abstract class AbstractUserDetailsReactiveAuthenticationManager
    implements ReactiveAuthenticationManager {
    AbstractUserDetailsReactiveAuthenticationManager();

    Mono<Authentication> authenticate(Authentication authentication);

    protected abstract Mono<UserDetails> retrieveUser(String username);
}

Key Methods:

protected abstract Mono<UserDetails> retrieveUser(String username)
  • Abstract method to reactively retrieve user details.

UserDetailsRepositoryReactiveAuthenticationManager

Reactive authentication provider using ReactiveUserDetailsService.

package org.springframework.security.authentication;

class UserDetailsRepositoryReactiveAuthenticationManager
    extends AbstractUserDetailsReactiveAuthenticationManager {
    UserDetailsRepositoryReactiveAuthenticationManager(
        ReactiveUserDetailsService userDetailsService);

    void setPasswordEncoder(PasswordEncoder passwordEncoder);
    void setUserDetailsPasswordService(
        ReactiveUserDetailsPasswordService passwordService);
    void setCompromisedPasswordChecker(
        ReactiveCompromisedPasswordChecker compromisedPasswordChecker);
}

Key Methods:

void setPasswordEncoder(PasswordEncoder passwordEncoder)
  • Sets the password encoder for credential verification.
void setUserDetailsPasswordService(
    ReactiveUserDetailsPasswordService passwordService)
  • Sets the reactive password update service.

Configuration Example:

@Configuration
public class ReactiveAuthConfig {

    @Bean
    public ReactiveAuthenticationManager authenticationManager(
        ReactiveUserDetailsService userDetailsService
    ) {
        UserDetailsRepositoryReactiveAuthenticationManager manager =
            new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);

        manager.setPasswordEncoder(passwordEncoder());
        manager.setUserDetailsPasswordService(reactivePasswordService());
        manager.setCompromisedPasswordChecker(reactiveCompromisedPasswordChecker());

        return manager;
    }

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

    @Bean
    public ReactiveUserDetailsPasswordService reactivePasswordService() {
        return new CustomReactivePasswordService();
    }

    @Bean
    public ReactiveCompromisedPasswordChecker reactiveCompromisedPasswordChecker() {
        return new ReactiveHaveIBeenPwnedPasswordChecker();
    }
}

Complete Application Example

// Domain Model
@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    private String username;
    private String password;
    private boolean enabled;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;

    @ElementCollection(fetch = FetchType.EAGER)
    private Set<String> roles;

    // getters and setters
}

// Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
    Optional<UserEntity> findByUsername(String username);
}

// UserDetailsService Implementation
@Service
public class DatabaseUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

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

        return User.builder()
            .username(user.getUsername())
            .password(user.getPassword())
            .authorities(user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .toList())
            .accountExpired(!user.isAccountNonExpired())
            .accountLocked(!user.isAccountNonLocked())
            .credentialsExpired(!user.isCredentialsNonExpired())
            .disabled(!user.isEnabled())
            .build();
    }
}

// Password Service Implementation
@Service
public class DatabaseUserDetailsPasswordService
    implements UserDetailsPasswordService {

    private final UserRepository userRepository;

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        UserEntity entity = userRepository.findByUsername(user.getUsername())
            .orElseThrow();

        entity.setPassword(newPassword);
        userRepository.save(entity);

        return User.builder()
            .username(user.getUsername())
            .password(newPassword)
            .authorities(user.getAuthorities())
            .build();
    }
}

// Security Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public DaoAuthenticationProvider authenticationProvider(
        DatabaseUserDetailsService userDetailsService,
        DatabaseUserDetailsPasswordService passwordService
    ) {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        provider.setUserDetailsPasswordService(passwordService);
        provider.setUserCache(userCache());
        provider.setHideUserNotFoundExceptions(true);
        return provider;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserCache userCache() {
        return new SpringCacheBasedUserCache(
            cacheManager().getCache("users")
        );
    }

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("users");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authenticationProvider(authenticationProvider(null, null))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(withDefaults());

        return http.build();
    }
}

Package

  • org.springframework.security.authentication.dao
  • org.springframework.security.core.userdetails
  • org.springframework.security.core.userdetails.cache