Custom Authentication Provider and UserDetailsService in Spring Security

πŸš€ Why Customize Authentication?

Spring Security provides powerful default authentication mechanisms. But real-world applications often need custom logic β€” such as validating users from a database, LDAP, or a third-party service. That’s where a Custom Authentication Provider and UserDetailsService shine.

With this setup, you gain full control over how credentials are verified and user data is retrieved.

Custom Authentication Provider and UserDetailsService

πŸ“¦ Maven Dependencies

Add the following to your pom.xml:




    org.springframework.boot
    spring-boot-starter-security



    org.springframework.boot
    spring-boot-starter-web


🧩 Project Structure

com.kscodes.springboot.security.customauth
β”œβ”€β”€ config/
β”‚ └── SecurityConfig.java
β”œβ”€β”€ controller/
β”‚ └── AuthController.java
β”œβ”€β”€ model/
β”‚ └── AppUser.java
β”œβ”€β”€ security/
β”‚ β”œβ”€β”€ CustomAuthenticationProvider.java
β”‚ └── CustomUserDetailsService.java
└── CustomAuthApplication.java

πŸ”§ Step 1: Create a User Entity

AppUser.java


package com.kscodes.springboot.security.customauth.model;

public class AppUser {
    private String username;
    private String password;
    private String role;

    public AppUser(String username, String password, String role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }

    // Getters and Setters
}

You can later connect this to a database.

πŸ” Step 2: Implement UserDetailsService

CustomUserDetailsService.java


package com.kscodes.springboot.security.customauth.security;

import com.kscodes.springboot.security.customauth.model.AppUser;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;

import org.springframework.stereotype.Service;

import java.util.Collections;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Mock user
        if (!username.equals("john")) {
            throw new UsernameNotFoundException("User not found");
        }

        AppUser user = new AppUser("john", "pass123", "ROLE_USER");

        return new User(
                user.getUsername(),
                user.getPassword(),
                Collections.singleton(new SimpleGrantedAuthority(user.getRole()))
        );
    }
}

πŸ›‘οΈ Step 3: Create a Custom Authentication Provider

CustomAuthenticationProvider.java


package com.kscodes.springboot.security.customauth.security;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.*;

import org.springframework.stereotype.Component;

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    public CustomAuthenticationProvider(CustomUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

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

        UserDetails user = userDetailsService.loadUserByUsername(username);

        if (!user.getPassword().equals(password)) {
            throw new BadCredentialsException("Invalid credentials");
        }

        return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
    }

    @Override
    public boolean supports(Class authType) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authType);
    }
}

πŸ” Step 4: Configure Spring Security

SecurityConfig.java


package com.kscodes.springboot.security.customauth.config;

import com.kscodes.springboot.security.customauth.security.CustomAuthenticationProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    private final CustomAuthenticationProvider authProvider;

    public SecurityConfig(CustomAuthenticationProvider authProvider) {
        this.authProvider = authProvider;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authenticationProvider(authProvider)
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/login").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(); // or httpBasic()

        return http.build();
    }

    @Bean
    public AuthenticationManager authManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

🌐 Step 5: Authentication Controller

AuthController.java


package com.kscodes.springboot.security.customauth.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    @GetMapping("/home")
    public String home() {
        return "Welcome to the secured home endpoint!";
    }
}

πŸ” Test It Out

Run the application and test login:

  • Visit: http://localhost:8080/home
  • Browser will prompt for login
  • Enter john / pass123

βœ… You’ll see the secured content if authentication succeeds using your Custom Authentication Provider and UserDetailsService.

⚠️ Best Practices

  • Use PasswordEncoder (e.g. BCryptPasswordEncoder) instead of plain password comparison.
  • Fetch users from a real database using repositories.
  • Add logging and throttling to prevent brute force attacks.

πŸ“š References