How to Implement Database‑Backed Authentication in Spring Security

This guide walks through configuring Spring Security with a custom AuthenticationProvider that authenticates users against a database, covering environment setup, core filter mechanics, custom UserDetails, UserDetailsService, PasswordEncoder implementations, and bean configuration with full code examples.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Implement Database‑Backed Authentication in Spring Security

Environment

Spring Boot 2.4.12 + Spring Security 5.4.9

Main Content

Implement database‑based user authentication using Spring Security.

Important Notes

Custom configuration example:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests().antMatchers("/resources/**", "/cache/**", "/process/login").permitAll();
        http.authorizeRequests().antMatchers("/demos/**").hasRole("USERS");
        http.authorizeRequests().antMatchers("/api/**").hasRole("ADMIN");
        http.formLogin();
    }
}

If no AuthenticationProvider bean or AuthenticationManager instance is defined, login attempts will cause a recursive loop because the framework cannot obtain an authentication manager.

// When a custom security config does not override configure(AuthenticationManagerBuilder auth)
// or does not set disableLocalConfigureAuthenticationBldr to false, the framework
// falls back to building the AuthenticationManager from the container.
public class AuthenticationConfiguration {
    public AuthenticationManager getAuthenticationManager() {
        // returns null if no bean is found
        this.authenticationManager = authBuilder.build();
        if (this.authenticationManager == null) {
            this.authenticationManager = getAuthenticationManagerBean();
        }
    }
    private AuthenticationManager getAuthenticationManagerBean() {
        return lazyBean(AuthenticationManager.class);
    }
    private <T> T lazyBean(Class<T> interfaceName) {
        LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
        String[] beanNamesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.applicationContext, interfaceName);
        if (beanNamesForType.length == 0) {
            return null;
        }
        String beanName = getBeanName(interfaceName, beanNamesForType);
        lazyTargetSource.setTargetBeanName(beanName);
        lazyTargetSource.setBeanFactory(this.applicationContext);
        ProxyFactoryBean proxyFactory = new ProxyFactoryBean();
        proxyFactory = this.objectPostProcessor.postProcess(proxyFactory);
        proxyFactory.setTargetSource(lazyTargetSource);
        return (T) proxyFactory.getObject();
    }
}

Database Authentication – Principle

Earlier articles used in‑memory users; real projects need to authenticate against a database. Spring Security relies on UsernamePasswordAuthenticationFilter to intercept login requests and delegate authentication to an AuthenticationProvider .

public abstract class AbstractAuthenticationProcessingFilter {
    private AuthenticationManager authenticationManager;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            Authentication authenticationResult = attemptAuthentication(request, response);
            if (authenticationResult == null) {
                return;
            }
            this.sessionStrategy.onAuthentication(authenticationResult, request, response);
            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
            successfulAuthentication(request, response, chain, authenticationResult);
        }
    }
    protected AuthenticationManager getAuthenticationManager() {
        return this.authenticationManager;
    }
}

The filter ultimately calls the AuthenticationProvider (e.g., DaoAuthenticationProvider ) to verify credentials.

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private PasswordEncoder passwordEncoder;
    private UserDetailsService userDetailsService;
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

Custom AuthenticationProvider

Instead of implementing AuthenticationProvider from scratch, extend DaoAuthenticationProvider and supply a UserDetailsService and a PasswordEncoder .

Steps

Define a POJO that implements UserDetails .

@Entity
@Table(name = "t_users")
public class Users implements UserDetails {
    @Id
    private String id;
    private String username;
    private String password;
    @Column(columnDefinition = "int default 1")
    private Integer enabled = 1;
    @Column(columnDefinition = "int default 0")
    private Integer locked = 0;
    private Collection<GrantedAuthority> authorities = new ArrayList<>();
    // getters, setters, and UserDetails methods omitted for brevity
}

Create a UserDetailsService that queries the database.

@Component
public class CustomUserDetailsService implements UserDetailsService {
    @Resource
    private UsersRepository usersRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return usersRepository.findByUsername(username);
    }
}

Implement a PasswordEncoder matching your password storage strategy.

@Component
public class CustomPasswordEncoder implements PasswordEncoder {
    // Example: plain‑text comparison (replace with real hashing as needed)
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.equals(encodedPassword);
    }
}

Register the provider as a bean.

@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder);
    return authenticationProvider;
}

Environment Preparation

Dependencies (Maven):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

DataSource configuration (application.yml):

spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/lua?serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
    username: root
    password: xxxxxx
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1

Summary

The article explains the underlying principle of database‑backed authentication in Spring Security, demonstrates how to customize the core authentication components (UserDetails, UserDetailsService, PasswordEncoder), and shows the bean configuration required to wire a DaoAuthenticationProvider into the security filter chain.

JavaSpring BootSpring SecurityDatabase AuthenticationCustom AuthenticationProvider
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.