Introduction

In this article, we’ll explore how to manually authenticate a user with Spring Security, using in-memory user details. We’ll cover the necessary configuration, code examples, and best practices to ensure a secure and scalable implementation.

For demonstration purposes, we’ll use two sample users with the following credentials:

  • User 1: username = “user1”, password = “password1”
  • User 2: username = “user2”, password = “password2”

1. Add the required dependencies

Before we dive into the implementation details, let’s add the required dependencies to our pom.xml file:

XML
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.0</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- Other dependencies -->
</dependencies>

Spring Security Configuration

First, let’s configure Spring Security in our SecurityConfig class with the necessary configurations required to manually authenticate a User with Spring Security. Here is the code:

Java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(
                User.withUsername("user1")
                        .password(passwordEncoder().encode("password1"))
                        .roles("USER")
                        .build()
        );
        userDetailsManager.createUser(
                User.withUsername("user2")
                        .password(passwordEncoder().encode("password2"))
                        .roles("USER")
                        .build()
        );
        return userDetailsManager;
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder())
                .and()
                .build();
    }

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests()
                .requestMatchers("/login", "/register", "/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .permitAll();
        return http.build();
    }
}

In this configuration, we define the following beans:

  1. userDetailsService: Provides an implementation of UserDetailsService using an InMemoryUserDetailsManager. We create two users (user1 and user2) with their respective passwords and the “USER” role.
  2. authenticationManager: Creates an AuthenticationManager instance by building an AuthenticationManagerBuilder and configuring the userDetailsService and passwordEncoder.
  3. passwordEncoder: Provides a strong password encoder implementation, BCryptPasswordEncoder.
  4. securityFilterChain: Configures the HTTP security rules, including permitting access to specific URLs like /login, /register, and /public/**, requiring authentication for all other requests, and setting up the form login and logout URLs.

2. Manually Authenticating a User in Spring Security

To manually authenticate a user, we need to create an instance of UsernamePasswordAuthenticationToken and pass it to the AuthenticationManager for authentication. Here’s an example implementation:

Java
@RestController
public class AuthenticationController {

    private final AuthenticationManager authenticationManager;

    public AuthenticationController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/authenticate")
    public ResponseEntity<String> authenticateUser(@RequestBody AuthenticationRequest request) {
        UsernamePasswordAuthenticationToken authToken =
                new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword());

        try {
            Authentication authentication = authenticationManager.authenticate(authToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            return ResponseEntity.ok("Authentication successful");
        } catch (BadCredentialsException ex) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password");
        }
    }

    static class AuthenticationRequest {
        private String username;
        private String password;

        // Getters and setters
    }
}

In this example, we have an AuthenticationController with a /authenticate endpoint that accepts a POST request containing the username and password in the request body.

  1. We create a UsernamePasswordAuthenticationToken instance with the provided username and password.
  2. We pass the authentication token to the AuthenticationManager for authentication using the authenticate method.
  3. If the authentication is successful, we store the Authentication object in the SecurityContextHolder.
  4. If the authentication fails with a BadCredentialsException, we return an UNAUTHORIZED response.

Testing the Authentication

To test the authentication process, we can use a tool like Postman or cURL to send a POST request to the /authenticate endpoint with the correct or incorrect user credentials.

For example, using cURL:

# Correct credentials (user1/password1)
curl -X POST -H "Content-Type: application/json" -d '{"username":"user1","password":"password1"}' http://localhost:8080/authenticate

# Correct credentials (user2/password2)
curl -X POST -H "Content-Type: application/json" -d '{"username":"user2","password":"password2"}' http://localhost:8080/authenticate

# Incorrect credentials
curl -X POST -H "Content-Type: application/json" -d '{"username":"user1","password":"wrongpassword"}' http://localhost:8080/authenticate

If the authentication is successful, you should receive the “Authentication successful” response. If the credentials are invalid, you should receive the “Invalid username or password” response.

Conclusion

In this article, we learned how to manually authenticate a user with Spring Security 6.2.x using in-memory user details. We covered the necessary configuration, code examples, and best practices for custom authentication scenarios.

Manually authenticating a user can be useful in scenarios where you need to integrate with a custom authentication mechanism or handle advanced authentication logic. Spring Security provides a flexible and extensible architecture that allows you to customize the authentication process according to your requirements.

While we used in-memory user details for demonstration purposes, in real-world applications, you would typically use a persistent data store like a database to store and retrieve user details. Spring Security supports various data sources and provides adapters for integrating with different authentication providers and user stores.

By |Last Updated: May 25th, 2024|Categories: Spring Security|