'Authentication Manager Builder in Spring Security

I was exploring spring security and tried to build a small application wherein I have a an entity name User and a userRepository having a one declared method findByUserName(String userName)

@Entity
@Table(name="user")
class User {
  @id
  private Long id;
  
  private String userName;

  private String password;

}

I have heard that spring security depends on principles and not users. so we have to have a class which implements UserDetails(provided by spring security). Whats the reason behind this.

Secondly once we have written all this code we need to configure it into a class which I have done as shown below

public class AppSecurityConfid extends WebSecurityCongigurerAdapter {
  // here we have to autowire the service class which we have made to call the 
     userRepository and find the user based on userName

  @Bean
  public DAOAuthenicationProvider authenicationProvider() {
      // wherein we create an instance and pass the autowired instance and set the 
         password encoder and return the instance

  }

   protected void configurer(AuthenticationManagerBuilder auth) {
       auth.authenticationProvider(authenticationProvider());
   }

} 

till here make sense but why we need Authentication Build Manager in these scheme of things



Solution 1:[1]

I am not an expert but I'd like to say something, maybe it can help:

Spring uses "in the background" a way to retrieve user data for authentication when you activate Spring Security. Of course, this method can be overriden so the developer can change how Spring obtains this data in order to support situations where the data is sparced in different tables, from a file, an API REST query, etc.

The authentication data is structured as a list, where each element corresponds to the data used to authenticate each user. This data is structured as a tuple of 3 elements: String username, String hashedPassword and boolean isAccountActive.

You need to provide a way to obtain this data for each user. You do not need to provide the data explicitly, just the way (method) to obtain it. One way to do it (as you said) is creating a class that implements UserDetailsService, which, for example, forces you to implement UserDetails loadUserByUsername(String email);. In this method you need to provide an instance of a class that implements UserDetails, which corresponds to the UserDetails of the User with the username passed as a parameter. This methods (and similar) are used by Spring Security "in the background" to retrieve the UserDetails of a certain user when is trying to access your web server.

If the Userdetails match with the credentials provided in the request, Spring will allow the request to hit your controllers; else it will throw a HTTP 401. Of course there are other authentication methods, but for the sake of simplicity we understand credentials as user / password with HTTP basic authentication.

So, why do we need a class that implements UserDetails? Because is the contract that a class needs to fulfill if it has to be used for internal authentication in Spring Security. Also to separate from a User class the logic of the business with the logic of the security. Probably creating your own class that extends UserDetails is the best idea, but is not actually necessary. For example if you have your own class to contain the data of a user, you just need to understand how to transform your User instance to UserDetails, so Spring Security can use it transparently and the opposite: how the UserDetails instance can be transformed into one of your users.

For example this is a method to obtain the User instance using the UserDetails instance that is currently authenticated in Spring Boot.

@Service
public class SecurityServiceClass{

    @Override
    public User getLoggedUser() {
        String username = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
        Optional<User> user = this.userService.get().stream().filter(r -> r.getEmail().equals(username)).findFirst();

        UserDetails userDetails = ((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal());

        // TODO: make error in case of null
        return user.orElse(new User());
    }
}

Here I retrieve the User by retrieving the username from the UserDetails and querying it to the DB to recover the User. I am accessing the DB using a repository class.

Here I do the opposite, transforming a User to a UserDetails by creating a Userdetails instance based on the relevant data of the User. (Note that I use the email as username)

@Service
public class UserServiceClass extends GenericServiceClass<User, UUID> {

    @Autowired
    public UserServiceClass(UserRepository userRepository) {
        super(userRepository);
    }


    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Optional<User> selected = ((UserRepository) this.genericRepository).getUserByEmail(s);

        if (selected.isPresent())
        {
            // Obtain user by email (username)
            User user = selected.get();

            // Obtain the roles of this user to construct the instance of UserDetails for SpringBoot Security.
            Set<Role> roles = user.getRoles();

            return org.springframework.security.core.userdetails.User
                    .withUsername(s)
                    .roles(roles.stream().toArray(
                        (n) -> {
                            return new String[n];
                        }
                    ))
                    .password(user.getHashedPassword())
                    .build();
        }
        else
        {
            throw new UsernameNotFoundException("The user with email " + s + " is not registered in the database");
        }
    }

Finally, regarding AuthenticationManagerBuilder: This is a method that is used to configure authentication. As far as I know, you can define how your application should obtain the UserDetails. I am not sure if you can provide a method or a lambda to retrieve the triplet for authentication String username, String hashedPassword and boolean isAccountActive. What I do know and did in my application is provide the SQL query used to retrieve the triplet from my DB since I have there all that information. Code:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private DataSource dataSource;


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationBuilder) throws Exception
    {
        Session session = this.sessionFactory.getCurrentSession();
        session.beginTransaction();

        authenticationBuilder.userDetailsService(this.userDetailsService()).passwordEncoder(this.passwordEncoder()).and()
                .jdbcAuthentication().dataSource(this.dataSource)
                .usersByUsernameQuery("select email, hashed_password as passw, true from user where email = ?")
                .authoritiesByUsernameQuery("SELECT user.email, CONCAT(elementpermission.journal_id, '_', elementpermission.authority)\n" +
                        "FROM user, elementpermission\n" +
                        "WHERE elementpermission.user = user.uuid \n" +
                        "AND user.email = ?");
                
        session.getTransaction().commit();
        session.close();
    }

TL;DR Spring Security needs instances that fulfill the contract of the interface UserDetails because is the interface that Spring Security uses to obtain the relevant data for authentication.

The authentication manager builder is used to config howto obtain the data used for authentication.

You can check this links if you want better information:

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Aleix Mariné