Spring Boot HandBook

    Introduction :#

    In a Spring Boot application, SecurityFilterAutoConfiguration automatically registers the DelegatingFilterProxy filter as springSecurityFilterChain. When a request reaches this filter, it delegates processing to the FilterChainProxy bean, which utilizes the SecurityFilterChain to execute a sequence of security filters. These filters are part of the core Spring Security components that manage authentication, authorization, and overall security within the application.

    Core Spring Security Components:#

    Core Spring Security components are used throughout a Spring Boot application to manage authentication, authorization, and overall security. Here’s where and how these components are typically used:

    • UserDetails

      • The UserDetails interface represents a user in the Spring Security framework. It provides methods to get user information such as username, password, and authorities.
      • Purpose: To encapsulate user information, including authentication and authorization details.
      • Implementation: You can use it to extend your User Entity.
         
    • UserDetailsService

      • The UserDetailsService interface is a core component in Spring Security that is used to retrieve user-related data. It has a single method: loadUserByUsername.
      • Purpose: To fetch user details from a datasource (e.g., database) based on the username.
      • Implementation: You typically implement this interface to load user details, such as username, password, and roles, from your own user repository.
         
    • InMemoryUserDetailsManager

      • The InMemoryUserDetailsManager is a Spring Security provided implementation of UserDetailsService that stores user information in memory.
      • Purpose: To store user details in memory, typically for testing or small applications. You define users directly in the configuration.
         
    • PasswordEncoder

      • The PasswordEncoder interface is used for encoding and validating passwords. It has methods for encoding raw passwords and matching encoded passwords.
      • Purpose: To securely hash passwords before storing them and to verify hashed passwords during authentication.
      • Common Implementations: 
        BCryptPasswordEncoder 
        Pbkdf2PasswordEncoder 
        SCryptPasswordEncoder
         

    Internal Working Flow of Spring Security:#

    This screenshot provides a flow of how authentication is handled in Spring Security:

    This screenshot depicts the general flow of how authentication is handled in Spring Security, starting from the HTTP request and moving through various components like the Security Filters Chain, AuthenticationManager, and finally storing the authenticated user in the SecurityContext.

    Let’s talking about in details:

    • HTTP Request:

      • An incoming HTTP request is received.
         
    • Security Filters Chain:

      • The request passes through a chain of security filters, such as:
        • WebAsyncManagerIntegrationFilter
        • SecurityContextPersistenceFilter
        • HeaderWriterFilter
        • LogoutFilter
        • UsernamePasswordAuthenticationFilter
        • ...and others.
           
    • AuthenticationManager:

      • The request's authentication responsibility is delegated to the AuthenticationManager. The AuthenticationManager is the central component that delegates authentication to various AuthenticationProviders.
         
    • AuthenticationProvider:

      • The AuthenticationManager uses one or more AuthenticationProviders to authenticate the request. Different AuthenticationProviders are used depending on the type of authentication required (e.g., DaoAuthenticationProvider, InMemoryAuthenticationProvider, OAuth2AuthenticationProvider, etc.).
         
    • Return Authenticated User:

      • If the authentication is successful, the authenticated user is returned to the AuthenticationManager.
         
    • SecurityContext:

      • The authenticated user is then set into the SecurityContext, which is stored in the SecurityContextHolder. The SecurityContext is now accessible throughout the application and is used to retrieve the authenticated user's details.
         
    • Dispatcher Servlet and Controllers:

      • Finally, the authenticated user is passed to the Dispatcher Servlet, which handles the request with the appropriate controllers.
         

    Zoomed in Flow:#

    This flow provides a more detailed breakdown of the components involved in the authentication process:

    Here, all the Rectangle boxes are the implementations by Spring Security and the dark boxes are that what I am going to implement.

    Pill boxes are interfaces.

    This screenshot gives a more detailed look into the specific components involved in the authentication process, such as AuthenticationManager, AuthenticationProvider, UserDetailsService, UserDetails, and PasswordEncoder, showing how they interact to authenticate a user. Let’s see:

    • Client /login:

      • The process begins when a client submits a login request.
         
    • AuthenticationManager:

      • The AuthenticationManager is responsible for authenticating the login request. It delegates this responsibility to a ProviderManager. Inside this AuthenticationManager we have a method that is called authenticate() and this method will take an object of Authentication (Authentication obj ). So, we will create an Authentication Object inside our controller only. That will contain our user information for example login, email and password.
        Basically, this AuthenticationManager is given the task to authenticate the login request.
      • If you go to the implementations part (5 implementations) of this interface you can see there is a class named ProviderManager .

         
    • ProviderManager:

      • The ProviderManager holds a list of AuthenticationProviders and loops through each one until authentication is successful or all have been tried.
      • This ProviderManager will implement authenticate() method.
      • If you go to this method you can see, this takes the providers iterator so, it goes to the each providers using the while loop. In this loop, you can see the AuthenticationProvider .
      • If you go to this particular AuthenticationProvider you will see that this AuthenticationProvider is an interface. Each AuthenticationProvider will implement this two methods authenticate() and supports() .
      • If you go to the implementations of this interface, you can see all of the implementing classes. Most of the important classes are DaoAuthenticationProvider and OidcAuthenticationRequestChecker .

        Here we talking about the DaoAuthenticationProvider class. The DaoAuthenticationProvider is commonly used to authenticate users from a database.
         
    • DaoAuthenticationProvider:

      • One of the AuthenticationProviders in Spring Security is DaoAuthenticationProvider.
      • This provider is useful when you want to authenticate users using their email as the "username" and their password. DaoAuthenticationProvider works by using a UserDetailsService to load user details, typically from a database or any other data source, to verify the credentials.
      • The UserDetailsService loads the user’s information and returns a UserDetails object, which includes the username, password, and authorities (roles or permissions).
      • After retrieving the user details, the DaoAuthenticationProvider compares the password provided by the user during login with the stored (encoded) password from the UserDetails object.
      • This comparison is done using a PasswordEncoder, like BCryptPasswordEncoder, to ensure that the password is securely checked.
      • If the password matches, the user is successfully authenticated, and the authentication information is stored in the SecurityContext, allowing access to the application.
      • If the password doesn’t match, an authentication exception is thrown, and the user is denied access.
         
    • UserDetailsService:

      The UserDetailsService is an interface that defines the method loadUserByUsername(String username), which is used to retrieve user details for authentication.
    import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class UserServiceImpl implements UserDetailsService { private final UserRepo userRepo; //here's the implemented method @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepo .findByEmail(username) // .orElse(null); //or you can throw an exception here .orElseThrow(()->new ResourceNotFoundException("USER WITH EMAIL "+username+" NOT FOUND")); } } // the repository interface import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository public interface UserRepo extends JpaRepository<UserEntity,Long> { //UserRepo extends JpaRepository<> interface Optional<UserEntity> findByEmail(String email); //create a method }
    • If it is a InMemoryUserDetailsManager class then it will get the source from the inmemory. Same for MyUserDetailsServiceImpl class.
    @Configuration public class WebSecurityConfig { //create a configuration class @Bean UserDetailsService myInMemoryUserDetailsService(){ UserDetails user = User //for user role .withUsername("<UserName>") // Replace <UserName> with the actual username .password(passwordEncoder().encode("<UserPassword>")) // Replace <UserPassword> with the actual password .roles("USER") .build(); UserDetails admin = User //for admin role .withUsername("<AdminName>") // Replace <AdminName> with the actual username .password(passwordEncoder().encode("<AdminPassword>")) // Replace <AdminPassword> with the actual password .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user,admin); } // Password Encoding @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
    • Example:

      We're setting up an InMemoryUserDetailsManager with a user named "user" and a password "password". The password is made secure using a special encoding method called BCryptPasswordEncoder. When someone tries to log in with the username "user", the system checks this in-memory list to find and verify the user's details.
       
    • UserDetails:

      • The UserDetails interface represents the user retrieved by the UserDetailsService. It includes methods like getUsername(), getPassword(), and getAuthorities() to provide the necessary information for authentication.

         
    • PasswordEncoder:

      • The PasswordEncoder (like BCryptPasswordEncoder) is used to encode or match passwords. It ensures that the password provided by the user matches the encoded password stored in the database. This PasswordEncoder is also an interface.
      • That contains two methods (encode() is used when a user signs up to create a secure version of their password and matches() is used during login to check if the provided password matches the stored secure version).

         
    • UserEntity:

      • The UserDetails is often implemented by a User entity class in your application, representing a user record in your database.
    import jakarta.persistence.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; @Entity public class UserEntity implements UserDetails { //UserEntity implements UserDetails interface @Id //for primary key @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true) //indicate that email should be unique private String email; private String password; private String name; //we have to implement these three methods @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.name; } }

     

    Last updated on Dec 09, 2024