DEV Community

Rajesh Mishra
Rajesh Mishra

Posted on Originally published at howtostartprogramming.in

Spring Security custom UserDetailsService with database example — Complete Guide

Spring Security custom UserDetailsService with database example — Complete Guide

A practical, in-depth guide to Spring Security custom UserDetailsService with database example with examples.

INTRO

When you start wiring Spring Security into a real‑world application, the default in‑memory user store quickly becomes a dead end. Hard‑coding usernames, passwords, and roles works for demos, but it collapses under the weight of multi‑tenant data, password‑policy changes, and audit requirements. The moment you need to pull credentials from a relational database, you’re forced to confront a gap in the official docs: how to bridge Spring Security’s UserDetailsService contract with JPA/Hibernate without reinventing the wheel.

That gap isn’t just academic. In production, a mis‑configured UserDetailsService can expose plaintext passwords, bypass role hierarchies, or cause subtle bugs that only appear under load. A clean, testable implementation that respects Spring’s transaction boundaries and leverages the power of Spring Data is essential for any serious backend. This article teases the exact steps you need to get a robust, database‑backed authentication layer up and running.

WHAT YOU'LL LEARN

  • How to model User and Role entities that satisfy Spring Security’s UserDetails contract.
  • Building a custom UserDetailsService that fetches users via Spring Data JPA, with proper exception handling.
  • Configuring PasswordEncoder and wiring the service into the security filter chain without XML.
  • Writing unit tests for the service using @DataJpaTest and Mockito to guarantee correctness.
  • Common pitfalls (lazy loading, circular dependencies, and password migration) and how to avoid them.
  • Production‑ready tips: caching user details, handling account lockout, and integrating with JWT or session‑based auth.

A SHORT CODE SNIPPET

@Service
public class CustomUserDetailsService implements UserDetailsService {

private final UserRepository userRepo;
private final PasswordEncoder passwordEncoder;

public CustomUserDetailsService(UserRepository userRepo,
PasswordEncoder passwordEncoder) {
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}

@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepo.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.isEnabled(),
true,
true,
!user.isLocked(),
mapRolesToAuthorities(user.getRoles()));
}

private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
.collect(Collectors.toSet());
}
}
Enter fullscreen mode Exit fullscreen mode

KEY TAKEAWAYS

  • A custom UserDetailsService is the bridge between Spring Security and any persistent store; keep it thin and delegate data access to Spring Data repositories.
  • Always annotate loadUserByUsername with @Transactional(readOnly = true) to avoid lazy‑loading exceptions during authentication.
  • Encode passwords with a strong PasswordEncoder (e.g., BCryptPasswordEncoder) and never store raw credentials.
  • Test the service in isolation and as part of the security configuration to catch integration bugs early.

👉 Read the complete guide with step-by-step examples, common mistakes, and production tips:

Spring Security custom UserDetailsService with database example — Complete Guide

Top comments (0)