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.
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 1Summary
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
