or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdindex.mdkotlin-dsl.mdmethod-security.mdoauth2.mdreactive-web-security.mdsaml2.mdservlet-web-security.md
tile.json

authentication.mddocs/

Authentication Configuration

Configure authentication mechanisms and user storage through AuthenticationManagerBuilder and related configurers. Supports in-memory, JDBC, LDAP, and custom authentication providers with thread-safe runtime behavior.

Capabilities

AuthenticationManagerBuilder

Builder for configuring AuthenticationManager. Not thread-safe during configuration. Thread-safe after build() is called.

package org.springframework.security.config.annotation.authentication.builders;

/**
 * Builder for configuring AuthenticationManager.
 * Not thread-safe during configuration. Thread-safe after build().
 * 
 * @since 3.2
 */
public class AuthenticationManagerBuilder
        extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
        implements ProviderManagerBuilder<AuthenticationManagerBuilder> {

    /**
     * Configures in-memory user store.
     * Useful for testing or simple applications.
     * 
     * @return configurer for in-memory authentication (never null)
     * @throws Exception if configuration fails
     */
    public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
        inMemoryAuthentication() throws Exception;

    /**
     * Configures JDBC-based user store.
     * Requires DataSource bean or explicit dataSource() call.
     * 
     * @return configurer for JDBC authentication (never null)
     * @throws Exception if configuration fails
     */
    public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder>
        jdbcAuthentication() throws Exception;

    /**
     * Configures custom UserDetailsService.
     * UserDetailsService must be thread-safe for concurrent access.
     * 
     * @param userDetailsService the service to use (must not be null)
     * @return configurer for UserDetailsService authentication (never null)
     * @throws Exception if configuration fails
     * @throws IllegalArgumentException if userDetailsService is null
     */
    public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T>
        userDetailsService(T userDetailsService) throws Exception;

    /**
     * Configures LDAP authentication.
     * Requires BaseLdapPathContextSource or explicit contextSource() call.
     * 
     * @return configurer for LDAP authentication (never null)
     * @throws Exception if configuration fails
     */
    public LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder>
        ldapAuthentication() throws Exception;

    /**
     * Adds custom AuthenticationProvider.
     * AuthenticationProvider must be thread-safe for concurrent use.
     * Multiple providers can be added - they are tried in order.
     * 
     * @param authenticationProvider the provider to add (must not be null)
     * @return this builder for method chaining
     * @throws IllegalArgumentException if authenticationProvider is null
     */
    public AuthenticationManagerBuilder authenticationProvider(
        AuthenticationProvider authenticationProvider
    );

    /**
     * Sets parent AuthenticationManager for fallback.
     * If no provider can authenticate, parent is consulted.
     * 
     * @param authenticationManager the parent manager (must not be null)
     * @return this builder for method chaining
     * @throws IllegalArgumentException if authenticationManager is null
     */
    public AuthenticationManagerBuilder parentAuthenticationManager(
        AuthenticationManager authenticationManager
    );

    /**
     * Configures whether to erase credentials after authentication.
     * Default: true (credentials are erased for security)
     * 
     * @param eraseCredentials true to erase credentials after authentication
     * @return this builder for method chaining
     */
    public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials);

    /**
     * Builds the AuthenticationManager.
     * Must be called to finalize configuration.
     * 
     * @return configured AuthenticationManager (never null)
     * @throws Exception if configuration is invalid or incomplete
     * @throws IllegalStateException if no authentication provider configured
     */
    public AuthenticationManager build() throws Exception;
}

Usage Pattern:

@Bean
public AuthenticationManager authenticationManager(
        HttpSecurity http,
        UserDetailsService userDetailsService,
        PasswordEncoder passwordEncoder) throws Exception {
    AuthenticationManagerBuilder authBuilder =
        http.getSharedObject(AuthenticationManagerBuilder.class);
    
    authBuilder
        .userDetailsService(userDetailsService)
        .passwordEncoder(passwordEncoder);
    
    return authBuilder.build();
}

Thread Safety:

  • Builder is not thread-safe during configuration
  • Built AuthenticationManager is thread-safe for concurrent authentication requests
  • Configure in @Bean methods (single-threaded during startup)

In-Memory Authentication

Configure in-memory user store for testing or simple applications. Thread-safe for concurrent access.

package org.springframework.security.config.annotation.authentication.configurers.provisioning;

/**
 * Configurer for in-memory user authentication.
 * Thread-safe: InMemoryUserDetailsManager is thread-safe.
 * 
 * @param <B> the builder type
 * @since 3.2
 */
public class InMemoryUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
        extends UserDetailsManagerConfigurer<B, InMemoryUserDetailsManagerConfigurer<B>> {

    /**
     * Adds a user with the specified username.
     * Returns builder for configuring user details.
     * 
     * @param username the username (must not be null or empty)
     * @return builder for configuring user details (never null)
     * @throws IllegalArgumentException if username is null or empty
     */
    public UserDetailsBuilder withUser(String username);

    /**
     * Adds a pre-configured UserDetails object.
     * 
     * @param userDetails the user details (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if userDetails is null
     */
    public InMemoryUserDetailsManagerConfigurer<B> withUser(UserDetails userDetails);

    /**
     * Builder for configuring user details.
     * Not thread-safe - use during configuration only.
     */
    public final class UserDetailsBuilder {
        /**
         * Sets the password.
         * Use password prefix format: {noop}password, {bcrypt}..., etc.
         * 
         * @param password the password (must not be null)
         * @return this builder for method chaining
         * @throws IllegalArgumentException if password is null
         */
        public UserDetailsBuilder password(String password);
        
        /**
         * Sets roles (automatically prefixed with ROLE_).
         * 
         * @param roles the roles (must not be null)
         * @return this builder for method chaining
         */
        public UserDetailsBuilder roles(String... roles);
        
        /**
         * Sets authorities (no automatic prefix).
         * 
         * @param authorities the authorities (must not be null)
         * @return this builder for method chaining
         */
        public UserDetailsBuilder authorities(String... authorities);
        
        /**
         * Sets authorities as GrantedAuthority objects.
         * 
         * @param authorities the authorities (must not be null)
         * @return this builder for method chaining
         */
        public UserDetailsBuilder authorities(GrantedAuthority... authorities);
        
        /**
         * Sets whether account is expired.
         * 
         * @param accountExpired true if account is expired
         * @return this builder for method chaining
         */
        public UserDetailsBuilder accountExpired(boolean accountExpired);
        
        /**
         * Sets whether account is locked.
         * 
         * @param accountLocked true if account is locked
         * @return this builder for method chaining
         */
        public UserDetailsBuilder accountLocked(boolean accountLocked);
        
        /**
         * Sets whether credentials are expired.
         * 
         * @param credentialsExpired true if credentials are expired
         * @return this builder for method chaining
         */
        public UserDetailsBuilder credentialsExpired(boolean credentialsExpired);
        
        /**
         * Sets whether account is disabled.
         * 
         * @param disabled true if account is disabled
         * @return this builder for method chaining
         */
        public UserDetailsBuilder disabled(boolean disabled);
        
        /**
         * Completes user configuration and returns to configurer.
         * 
         * @return the configurer for adding more users or finalizing
         */
        public InMemoryUserDetailsManagerConfigurer<B> and();
    }
}

Usage Examples:

// Recommended: Configure via UserDetailsService bean
@Bean
public UserDetailsService userDetailsService() {
    UserDetails user = User.withUsername("user")
        .password("{noop}password")  // {noop} = no encoding (testing only)
        .roles("USER")
        .build();

    UserDetails admin = User.withUsername("admin")
        .password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
        .roles("USER", "ADMIN")
        .build();

    return new InMemoryUserDetailsManager(user, admin);
}

// Using AuthenticationManagerBuilder (less common)
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
    AuthenticationManagerBuilder authBuilder =
        http.getSharedObject(AuthenticationManagerBuilder.class);

    authBuilder
        .inMemoryAuthentication()
            .withUser("user")
                .password("{noop}password")
                .roles("USER")
                .and()
            .withUser("admin")
                .password("{noop}admin")
                .roles("USER", "ADMIN")
                .accountExpired(false)
                .accountLocked(false)
                .credentialsExpired(false)
                .disabled(false);

    return authBuilder.build();
}

// With password encoder
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
    return new InMemoryUserDetailsManager(
        User.withUsername("user")
            .password(encoder.encode("password"))
            .roles("USER")
            .build()
    );
}

Important Notes:

  • In-memory storage is lost on application restart
  • Use only for testing or simple applications
  • For production, use JDBC or custom UserDetailsService
  • Password prefixes: {noop}, {bcrypt}, {pbkdf2}, {scrypt}, {sha256}
  • {noop} means no encoding - never use in production

Thread Safety:

  • InMemoryUserDetailsManager is thread-safe for concurrent access
  • User lookups are thread-safe

Performance:

  • Fastest authentication method (~0.1ms per lookup)
  • No network or database overhead
  • Memory usage: ~1KB per user

JDBC Authentication

Configure JDBC-based user store with database. Thread-safe if DataSource and queries are thread-safe.

package org.springframework.security.config.annotation.authentication.configurers.provisioning;

/**
 * Configurer for JDBC-based user authentication.
 * Thread-safe: JdbcUserDetailsManager is thread-safe if DataSource is thread-safe.
 * 
 * @param <B> the builder type
 * @since 3.2
 */
public class JdbcUserDetailsManagerConfigurer<B extends ProviderManagerBuilder<B>>
        extends UserDetailsManagerConfigurer<B, JdbcUserDetailsManagerConfigurer<B>> {

    /**
     * Sets the DataSource for database access.
     * 
     * @param dataSource the data source (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if dataSource is null
     */
    public JdbcUserDetailsManagerConfigurer<B> dataSource(DataSource dataSource);

    /**
     * Sets custom query for loading users.
     * Query must return: username, password, enabled (in that order).
     * 
     * @param query the SQL query (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if query is null
     */
    public JdbcUserDetailsManagerConfigurer<B> usersByUsernameQuery(String query);

    /**
     * Sets custom query for loading authorities.
     * Query must return: username, authority (in that order).
     * 
     * @param query the SQL query (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if query is null
     */
    public JdbcUserDetailsManagerConfigurer<B> authoritiesByUsernameQuery(String query);

    /**
     * Sets custom query for loading group authorities.
     * Query must return: id, group_name, authority (in that order).
     * 
     * @param query the SQL query (must not be null)
     * @return this configurer for method chaining
     */
    public JdbcUserDetailsManagerConfigurer<B> groupAuthoritiesByUsername(String query);

    /**
     * Sets the role prefix (default: "ROLE_").
     * 
     * @param rolePrefix the role prefix (must not be null)
     * @return this configurer for method chaining
     */
    public JdbcUserDetailsManagerConfigurer<B> rolePrefix(String rolePrefix);

    /**
     * Sets the password encoder.
     * Required if passwords are encoded in database.
     * 
     * @param passwordEncoder the encoder (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if passwordEncoder is null
     */
    public JdbcUserDetailsManagerConfigurer<B> passwordEncoder(PasswordEncoder passwordEncoder);

    /**
     * Uses default Spring Security schema.
     * Creates users and authorities tables if they don't exist.
     * 
     * @return this configurer for method chaining
     */
    public JdbcUserDetailsManagerConfigurer<B> withDefaultSchema();
}

Usage Examples:

// Recommended: Configure via UserDetailsManager bean
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    // Uses default schema and queries
    return manager;
}

// Custom queries
@Bean
public UserDetailsManager customJdbcUserDetailsManager(
        DataSource dataSource,
        PasswordEncoder encoder) {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    manager.setUsersByUsernameQuery(
        "SELECT username, password, enabled FROM custom_users WHERE username = ?"
    );
    manager.setAuthoritiesByUsernameQuery(
        "SELECT username, authority FROM custom_authorities WHERE username = ?"
    );
    manager.setPasswordEncoder(encoder);
    return manager;
}

// Using AuthenticationManagerBuilder
@Bean
public AuthenticationManager authenticationManager(
        HttpSecurity http,
        DataSource dataSource,
        PasswordEncoder encoder) throws Exception {
    AuthenticationManagerBuilder authBuilder =
        http.getSharedObject(AuthenticationManagerBuilder.class);

    authBuilder
        .jdbcAuthentication()
            .dataSource(dataSource)
            .passwordEncoder(encoder)
            .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?")
            .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?");

    return authBuilder.build();
}

// Default schema (for withDefaultSchema())
/*
CREATE TABLE users (
    username VARCHAR(50) NOT NULL PRIMARY KEY,
    password VARCHAR(500) NOT NULL,
    enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
    username VARCHAR(50) NOT NULL,
    authority VARCHAR(50) NOT NULL,
    FOREIGN KEY (username) REFERENCES users(username)
);

CREATE UNIQUE INDEX ix_auth_username ON authorities (username, authority);
*/

Important Notes:

  • DataSource must be thread-safe (most connection pools are)
  • Queries must use parameterized statements (prevent SQL injection)
  • Password encoder must match password format in database
  • Default queries expect specific column order
  • Use connection pooling for better performance

Thread Safety:

  • JdbcUserDetailsManager is thread-safe if DataSource is thread-safe
  • Database queries are executed per authentication request
  • Connection pooling handles concurrency

Performance:

  • Database query adds ~1-10ms overhead per authentication
  • Use connection pooling to reduce overhead
  • Index username column for faster lookups
  • Consider caching for frequently accessed users

UserDetailsService Configuration

Configure authentication with custom UserDetailsService. UserDetailsService must be thread-safe.

package org.springframework.security.config.annotation.authentication.configurers.userdetails;

/**
 * Configurer for UserDetailsService-based authentication.
 * Thread-safe: Depends on UserDetailsService thread safety.
 * 
 * @param <B> the builder type
 * @param <U> the UserDetailsService type
 * @since 3.2
 */
public class DaoAuthenticationConfigurer<B extends ProviderManagerBuilder<B>, U extends UserDetailsService>
        extends AbstractDaoAuthenticationConfigurer<B, DaoAuthenticationConfigurer<B, U>, U> {

    /**
     * Sets the UserDetailsService.
     * UserDetailsService must be thread-safe for concurrent access.
     * 
     * @param userDetailsService the service (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if userDetailsService is null
     */
    public DaoAuthenticationConfigurer<B, U> userDetailsService(U userDetailsService);

    /**
     * Sets the password encoder.
     * Required if passwords are encoded.
     * 
     * @param passwordEncoder the encoder (must not be null)
     * @return this configurer for method chaining
     * @throws IllegalArgumentException if passwordEncoder is null
     */
    public DaoAuthenticationConfigurer<B, U> passwordEncoder(PasswordEncoder passwordEncoder);

    /**
     * Sets the password manager for password updates.
     * 
     * @param passwordManager the manager (must not be null)
     * @return this configurer for method chaining
     */
    public DaoAuthenticationConfigurer<B, U> userDetailsPasswordManager(
        UserDetailsPasswordService passwordManager
    );
}

Usage Example:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) 
            throws UsernameNotFoundException {
        // Must be thread-safe for concurrent access
        com.example.User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));

        return User.withUsername(user.getUsername())
            .password(user.getPassword())  // Should be encoded
            .authorities(user.getAuthorities())
            .accountExpired(!user.isAccountNonExpired())
            .accountLocked(!user.isAccountNonLocked())
            .credentialsExpired(!user.isCredentialsNonExpired())
            .disabled(!user.isEnabled())
            .build();
    }
}

// Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .userDetailsService(userDetailsService)  // Sets UserDetailsService
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());

        return http.build();
    }

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

Important Notes:

  • UserDetailsService must be thread-safe
  • loadUserByUsername() is called for every authentication attempt
  • Throw UsernameNotFoundException if user not found
  • Return fully configured UserDetails object
  • Password should match format expected by PasswordEncoder

Thread Safety:

  • UserDetailsService implementation must be thread-safe
  • loadUserByUsername() may be called concurrently
  • Use synchronized access or thread-safe data structures

Performance:

  • Performance depends on implementation
  • Database-backed: ~1-10ms per lookup
  • Caching can improve performance significantly
  • Consider CachingUserDetailsService wrapper

LDAP Authentication

Configure LDAP authentication. Thread-safe if LDAP context source is thread-safe.

package org.springframework.security.config.annotation.authentication.configurers.ldap;

/**
 * Configurer for LDAP authentication.
 * Thread-safe: Depends on BaseLdapPathContextSource thread safety.
 * 
 * @param <B> the builder type
 * @since 3.2
 */
public class LdapAuthenticationProviderConfigurer<B extends ProviderManagerBuilder<B>>
        extends AbstractDaoAuthenticationConfigurer<B, LdapAuthenticationProviderConfigurer<B>,
                                                    UserDetailsService> {

    /**
     * Sets user DN patterns for direct bind.
     * Pattern {0} is replaced with username.
     * Example: "uid={0},ou=people"
     * 
     * @param userDnPatterns the patterns (must not be null)
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> userDnPatterns(String... userDnPatterns);

    /**
     * Sets base DN for user search.
     * 
     * @param userSearchBase the base DN (must not be null)
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> userSearchBase(String userSearchBase);

    /**
     * Sets LDAP filter for user search.
     * Filter {0} is replaced with username.
     * Example: "(uid={0})"
     * 
     * @param userSearchFilter the filter (must not be null)
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> userSearchFilter(String userSearchFilter);

    /**
     * Sets base DN for group search.
     * 
     * @param groupSearchBase the base DN
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> groupSearchBase(String groupSearchBase);

    /**
     * Sets LDAP filter for group search.
     * 
     * @param groupSearchFilter the filter
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> groupSearchFilter(String groupSearchFilter);

    /**
     * Sets attribute name for group role.
     * 
     * @param groupRoleAttribute the attribute name
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> groupRoleAttribute(String groupRoleAttribute);

    /**
     * Sets role prefix (default: "ROLE_").
     * 
     * @param rolePrefix the prefix
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> rolePrefix(String rolePrefix);

    /**
     * Sets LDAP context source.
     * 
     * @param contextSource the context source (must not be null)
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> contextSource(
        BaseLdapPathContextSource contextSource
    );

    /**
     * Configures password comparison (instead of bind).
     * 
     * @return password comparison configurer
     */
    public PasswordComparisonConfigurer<B> passwordCompare();

    /**
     * Sets password encoder for password comparison.
     * 
     * @param passwordEncoder the encoder
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> passwordEncoder(PasswordEncoder passwordEncoder);

    /**
     * Sets password attribute name.
     * 
     * @param passwordAttribute the attribute name
     * @return this configurer for method chaining
     */
    public LdapAuthenticationProviderConfigurer<B> passwordAttribute(String passwordAttribute);

    /**
     * Configurer for password comparison.
     */
    public final class PasswordComparisonConfigurer<B2 extends ProviderManagerBuilder<B2>> {
        public PasswordComparisonConfigurer<B2> passwordEncoder(PasswordEncoder passwordEncoder);
        public PasswordComparisonConfigurer<B2> passwordAttribute(String passwordAttribute);
        public LdapAuthenticationProviderConfigurer<B2> and();
    }
}

Usage Examples:

// Using Spring Boot auto-configuration
// application.yml
spring:
  ldap:
    urls: ldap://ldap.example.com:389
    base: dc=example,dc=com

@Configuration
@EnableWebSecurity
public class LdapSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());
        return http.build();
    }
}

// Manual LDAP configuration with bind
@Bean
public AuthenticationManager authenticationManager(
        BaseLdapPathContextSource contextSource) throws Exception {
    LdapBindAuthenticationManagerFactory factory =
        new LdapBindAuthenticationManagerFactory(contextSource);

    factory.setUserDnPatterns("uid={0},ou=people");
    factory.setUserSearchBase("ou=people");
    factory.setUserSearchFilter("(uid={0})");

    return factory.createAuthenticationManager();
}

// LDAP with password comparison
@Bean
public AuthenticationManager ldapAuthenticationManager(
        BaseLdapPathContextSource contextSource,
        PasswordEncoder encoder) {
    LdapPasswordComparisonAuthenticationManagerFactory factory =
        new LdapPasswordComparisonAuthenticationManagerFactory(
            contextSource,
            encoder
        );

    factory.setUserDnPatterns("uid={0},ou=people");
    factory.setPasswordAttribute("userPassword");

    return factory.createAuthenticationManager();
}

// Embedded LDAP server (for testing)
@Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
    EmbeddedLdapServerContextSourceFactoryBean factory =
        EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
    factory.setPort(8389);
    return factory;
}

Important Notes:

  • LDAP bind is faster than password comparison
  • Password comparison requires reading password from LDAP
  • Use connection pooling for better performance
  • Handle LDAP connection failures gracefully

Thread Safety:

  • LDAP context source must be thread-safe
  • Connection pooling handles concurrency
  • LDAP operations are thread-safe

Performance:

  • LDAP bind: ~10-50ms per authentication
  • Password comparison: ~20-100ms (requires read + compare)
  • Network latency is main factor
  • Connection pooling reduces overhead

Custom Authentication Provider

Add custom authentication provider. Must be thread-safe for concurrent use.

/**
 * Interface for custom authentication providers.
 * Must be thread-safe for concurrent use.
 * 
 * @since 3.0
 */
public interface AuthenticationProvider {
    /**
     * Authenticates the given authentication object.
     * 
     * @param authentication the authentication to authenticate (must not be null)
     * @return fully authenticated Authentication object (never null)
     * @throws AuthenticationException if authentication fails
     */
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
    /**
     * Returns whether this provider supports the given authentication type.
     * 
     * @param authentication the authentication class
     * @return true if this provider supports the authentication type
     */
    boolean supports(Class<?> authentication);
}

Usage Example:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserService userService;

    @Override
    public Authentication authenticate(Authentication authentication) 
            throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // Custom authentication logic - must be thread-safe
        User user = userService.findByUsername(username);
        if (user == null || !userService.checkPassword(password, user.getPassword())) {
            throw new BadCredentialsException("Invalid credentials");
        }

        // Check account status
        if (!user.isEnabled()) {
            throw new DisabledException("Account is disabled");
        }
        if (!user.isAccountNonLocked()) {
            throw new LockedException("Account is locked");
        }

        // Create authenticated token
        List<GrantedAuthority> authorities = userService.getAuthorities(user);
        return new UsernamePasswordAuthenticationToken(
            username, password, authorities
        );
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class
            .isAssignableFrom(authentication);
    }
}

// Configuration
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomAuthenticationProvider authenticationProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authenticationProvider(authenticationProvider)  // Register provider
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());

        return http.build();
    }
}

Important Notes:

  • AuthenticationProvider must be thread-safe
  • supports() determines which authentication types are handled
  • Throw appropriate AuthenticationException subclasses
  • Return fully authenticated Authentication object
  • Multiple providers are tried in order until one succeeds

Thread Safety:

  • authenticate() may be called concurrently
  • Use thread-safe data structures
  • Synchronize access to shared mutable state

Performance:

  • Depends on implementation
  • Consider caching for expensive operations
  • Fail fast for invalid credentials

Password Encoding

Configure password encoders for secure password storage. Password encoders are thread-safe.

/**
 * Interface for encoding and validating passwords.
 * Implementations must be thread-safe.
 * 
 * @since 3.1
 */
public interface PasswordEncoder {
    /**
     * Encodes the raw password.
     * 
     * @param rawPassword the raw password (must not be null)
     * @return encoded password (never null)
     */
    String encode(CharSequence rawPassword);
    
    /**
     * Validates if raw password matches encoded password.
     * 
     * @param rawPassword the raw password (must not be null)
     * @param encodedPassword the encoded password (must not be null)
     * @return true if passwords match
     */
    boolean matches(CharSequence rawPassword, String encodedPassword);
    
    /**
     * Returns whether encoded password should be upgraded.
     * 
     * @param encodedPassword the encoded password
     * @return true if password should be upgraded
     */
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

Usage Examples:

// BCrypt (recommended for most applications)
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
    // Default strength: 10
    // Higher strength = more secure but slower
}

// BCrypt with custom strength
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);  // Strength 4-31
}

// Delegating password encoder (supports multiple formats)
@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    // Supports: {bcrypt}, {noop}, {pbkdf2}, {scrypt}, {sha256}, etc.
    // Automatically detects format from password prefix
}

// Using password encoder
@Service
public class UserService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void createUser(String username, String rawPassword) {
        String encodedPassword = passwordEncoder.encode(rawPassword);
        // Save user with encoded password
        userRepository.save(new User(username, encodedPassword));
    }

    public boolean checkPassword(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}

// Password formats with delegating encoder
// {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
// {noop}password  // Plain text (testing only, never use in production)
// {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
// {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=

Important Notes:

  • Never store plain-text passwords
  • BCrypt is recommended for most applications
  • Use delegating encoder for migration scenarios
  • {noop} prefix means no encoding - testing only
  • Password encoding is CPU-intensive - consider async for user registration

Thread Safety:

  • All PasswordEncoder implementations are thread-safe
  • encode() and matches() are thread-safe for concurrent use

Performance:

  • BCrypt encoding: ~100-500ms (depends on strength)
  • BCrypt matching: ~50-200ms
  • Use strength 10-12 for good balance of security and performance
  • Consider async encoding for user registration

Types

/**
 * User information interface.
 * Immutable implementations are thread-safe.
 * 
 * @since 2.0
 */
public interface UserDetails extends Serializable {
    /**
     * Returns user authorities.
     * 
     * @return authorities (never null)
     */
    Collection<? extends GrantedAuthority> getAuthorities();
    
    /**
     * Returns user password (may be encoded).
     * 
     * @return password (may be null)
     */
    String getPassword();
    
    /**
     * Returns username.
     * 
     * @return username (never null)
     */
    String getUsername();
    
    /**
     * Returns whether account is non-expired.
     * 
     * @return true if account is non-expired
     */
    boolean isAccountNonExpired();
    
    /**
     * Returns whether account is non-locked.
     * 
     * @return true if account is non-locked
     */
    boolean isAccountNonLocked();
    
    /**
     * Returns whether credentials are non-expired.
     * 
     * @return true if credentials are non-expired
     */
    boolean isCredentialsNonExpired();
    
    /**
     * Returns whether account is enabled.
     * 
     * @return true if account is enabled
     */
    boolean isEnabled();
}

/**
 * Interface for loading user-specific data.
 * Must be thread-safe for concurrent access.
 * 
 * @since 2.0
 */
public interface UserDetailsService {
    /**
     * Loads user by username.
     * 
     * @param username the username (must not be null)
     * @return user details (never null)
     * @throws UsernameNotFoundException if user not found
     */
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

/**
 * Interface for managing user details.
 * Extends UserDetailsService with user management operations.
 * Must be thread-safe for concurrent access.
 * 
 * @since 2.0
 */
public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
}

/**
 * Interface for encoding and validating passwords.
 * Must be thread-safe.
 * 
 * @since 3.1
 */
public interface PasswordEncoder {
    String encode(CharSequence rawPassword);
    boolean matches(CharSequence rawPassword, String encodedPassword);
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

/**
 * Authentication object interface.
 * Represents authentication request or result.
 * 
 * @since 2.0
 */
public interface Authentication extends Principal, Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    Object getCredentials();
    Object getDetails();
    Object getPrincipal();
    boolean isAuthenticated();
    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

/**
 * Granted authority interface.
 * Represents an authority granted to an authentication.
 * 
 * @since 2.0
 */
public interface GrantedAuthority extends Serializable {
    String getAuthority();
}

Complete Example

@Configuration
@EnableWebSecurity
public class CompleteAuthenticationConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults());

        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        // In production, this would be a database-backed implementation
        UserDetails user = User.withUsername("user")
            .password(passwordEncoder.encode("password"))
            .roles("USER")
            .build();

        UserDetails admin = User.withUsername("admin")
            .password(passwordEncoder.encode("admin"))
            .roles("USER", "ADMIN")
            .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

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

    // Optional: Custom authentication manager
    @Bean
    public AuthenticationManager authenticationManager(
            UserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder) {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder);
        return new ProviderManager(authProvider);
    }
}

Troubleshooting

Issue: Authentication always fails

Symptoms: Login fails even with correct credentials.

Causes:

  1. Password encoder mismatch
  2. UserDetailsService not configured
  3. Password format incorrect
  4. Account disabled/expired/locked

Solutions:

// Ensure password encoder matches
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

// Ensure UserDetailsService uses same encoder
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
    return new InMemoryUserDetailsManager(
        User.withUsername("user")
            .password(encoder.encode("password"))  // Encode password
            .roles("USER")
            .build()
    );
}

// Or use password prefix
User.withUsername("user")
    .password("{bcrypt}$2a$10$...")  // Pre-encoded with prefix
    .roles("USER")
    .build();

// Check account status
User.withUsername("user")
    .password(encoder.encode("password"))
    .roles("USER")
    .accountExpired(false)  // Ensure account is not expired
    .accountLocked(false)   // Ensure account is not locked
    .credentialsExpired(false)  // Ensure credentials not expired
    .disabled(false)  // Ensure account is enabled
    .build();

Issue: UserDetailsService not found

Symptoms: UsernameNotFoundException or authentication fails with "User not found".

Causes:

  1. UserDetailsService bean not defined
  2. UserDetailsService not registered with HttpSecurity
  3. User doesn't exist in store

Solutions:

// Ensure UserDetailsService bean exists
@Bean
public UserDetailsService userDetailsService() {
    return new InMemoryUserDetailsManager(/* users */);
}

// Ensure UserDetailsService is registered
@Bean
public SecurityFilterChain filterChain(
        HttpSecurity http,
        UserDetailsService userDetailsService) throws Exception {
    http
        .userDetailsService(userDetailsService)  // Register service
        .authorizeHttpRequests(authorize -> authorize
            .anyRequest().authenticated()
        );
    return http.build();
}

Issue: Password encoding fails

Symptoms: Password encoding throws exception or doesn't work.

Causes:

  1. Password encoder not configured
  2. Invalid password format
  3. Encoding algorithm not available

Solutions:

// Ensure password encoder bean exists
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

// Use delegating encoder for multiple formats
@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

// Handle encoding errors
try {
    String encoded = passwordEncoder.encode(rawPassword);
} catch (Exception e) {
    // Handle encoding failure
    logger.error("Password encoding failed", e);
}

Issue: JDBC authentication fails

Symptoms: Database authentication fails with SQL errors.

Causes:

  1. Wrong SQL queries
  2. Missing database tables
  3. DataSource not configured
  4. Column order mismatch

Solutions:

// Ensure DataSource is configured
@Bean
public DataSource dataSource() {
    // Configure DataSource
}

// Use default schema
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
    JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
    manager.setCreateUserSql("INSERT INTO users (username, password, enabled) VALUES (?, ?, ?)");
    manager.setCreateAuthoritySql("INSERT INTO authorities (username, authority) VALUES (?, ?)");
    return manager;
}

// Verify query column order
// usersByUsernameQuery must return: username, password, enabled
// authoritiesByUsernameQuery must return: username, authority
manager.setUsersByUsernameQuery(
    "SELECT username, password, enabled FROM users WHERE username = ?"
);
manager.setAuthoritiesByUsernameQuery(
    "SELECT username, authority FROM authorities WHERE username = ?"
);

Performance Considerations

Authentication Performance

  • In-Memory: Fastest (~0.1ms per authentication)
  • JDBC: Moderate (~1-10ms per authentication, depends on query)
  • LDAP: Slower (~10-100ms per authentication, depends on network)
  • Custom Provider: Depends on implementation

Optimization Tips

  1. Use connection pooling for JDBC and LDAP
  2. Cache user lookups when appropriate (use CachingUserDetailsService)
  3. Index database columns used in authentication queries
  4. Use async password encoding for user registration
  5. Minimize LDAP queries - use bind when possible
  6. Consider caching for frequently accessed users
  7. Use appropriate password encoder strength - balance security and performance

Thread Safety

Configuration Phase

  • AuthenticationManagerBuilder: Not thread-safe during configuration
  • Configurers: Not thread-safe during configuration
  • Configure in @Bean methods (single-threaded during startup)

Runtime Phase

  • AuthenticationManager: Thread-safe for concurrent authentication requests
  • UserDetailsService: Must be thread-safe for concurrent access
  • AuthenticationProvider: Must be thread-safe for concurrent use
  • PasswordEncoder: Thread-safe for concurrent encoding/matching

Best Practices:

  • Configure authentication in @Bean methods
  • Ensure all runtime components are thread-safe
  • Use thread-safe data structures in custom implementations
  • Synchronize access to shared mutable state