'Can i use two different tables for login in my spring boot application by spring security?

In my current project I have two separate entities.

  1. User :- TO authenticate users
  2. Customer :- To authenticate customers

I'm confuse How will we manage login process in same project for two separate entities by spring security?

As of now its working with one User entity , now i have to integrate one more login for customers with the help of Customer table.

is it possible ?

Note :- Due to some another requirement i can't use the same table for both users and customer.

I am sharing some code for more clearance.

Below code is the implementation of user detail service.

private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class);

private final UserLoginRepository userRepository;

public DomainUserDetailsService(UserLoginRepository userRepository) {
    this.userRepository = userRepository;
}

@Override
@Transactional
public UserDetails loadUserByUsername(final String login) {
    log.debug("Authenticating {}", login);

    if (new EmailValidator().isValid(login, null)) {
        Optional<User> userByEmailFromDatabase = userRepository.findOneWithAuthoritiesByLogin(login);
        return userByEmailFromDatabase.map(user -> createSpringSecurityUser(login, user))
            .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database"));
    }

    String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
    Optional<User> userByLoginFromDatabase = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin);
    return userByLoginFromDatabase.map(user -> createSpringSecurityUser(lowercaseLogin, user))
        .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database"));

}

private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) {

    List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
        .map(authority -> new SimpleGrantedAuthority(authority.getName()))
        .collect(Collectors.toList());
    return new org.springframework.security.core.userdetails.User(user.getLogin(),
        user.getPassword(),
        grantedAuthorities);
}

}

Below is the implantation of security config class.

private final AuthenticationManagerBuilder authenticationManagerBuilder;

private final UserDetailsService userDetailsService;

private final TokenProvider tokenProvider;

private final CorsFilter corsFilter;

private final SecurityProblemSupport problemSupport;

public SecurityConfiguration(AuthenticationManagerBuilder authenticationManagerBuilder, UserDetailsService userDetailsService,TokenProvider tokenProvider,CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
    this.authenticationManagerBuilder = authenticationManagerBuilder;
    this.userDetailsService = userDetailsService;
    this.tokenProvider = tokenProvider;
    this.corsFilter = corsFilter;
    this.problemSupport = problemSupport;
}

@PostConstruct
public void init() {
    try {
        authenticationManagerBuilder
            .userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    } catch (Exception e) {
        throw new BeanInitializationException("Security configuration failed", e);
    }
}

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

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

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .antMatchers("/app/**/*.{js,html}")
        .antMatchers("/i18n/**")
        .antMatchers("/content/**")
        .antMatchers("/swagger-ui/index.html");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
        .exceptionHandling()
        .authenticationEntryPoint(problemSupport)
        .accessDeniedHandler(problemSupport)
    .and()
        .csrf()
        .disable()
        .headers()
        .frameOptions()
        .disable()
    .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
        .authorizeRequests()
        .antMatchers("/api/register").permitAll()
        .antMatchers("/api/activate").permitAll()
        .antMatchers("/api/userLogin").permitAll()
        .antMatchers("/api/account/reset-password/init").permitAll()
        .antMatchers("/api/account/reset-password/finish").permitAll()
        .antMatchers("/api/**").authenticated()
        .antMatchers("/management/health").permitAll()
        .antMatchers("/management/info").permitAll()
        .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
        .antMatchers("/v2/api-docs/**").permitAll()
        .antMatchers("/swagger-resources/configuration/ui").permitAll()
        .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN)
    .and()
        .apply(securityConfigurerAdapter());

}

private JWTConfigurer securityConfigurerAdapter() {
    return new JWTConfigurer(tokenProvider);
}

}

Login api

@PostMapping("/userLogin")
@Timed
public Response<JWTToken> authorize(
        @Valid @RequestBody UserLoginReq userLoginReq) {

    Map<String, Object> responseData = null;
    try {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                userLoginReq.getUsername(), userLoginReq.getPassword());

        Authentication authentication = this.authenticationManager
                .authenticate(authenticationToken);

        SecurityContextHolder.getContext()
                .setAuthentication(authentication);

}



Solution 1:[1]

Yes you can pass user type combined in userName, separated by a character like :

Example:

  1. String userName=inputUserName +":APP_USER";

  2. UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, password);

  3. in UserDetailsService.loadUserByUsername(userName) first split get the userName part and also get userType part. Now on userType base you can decide the user should be authenticate from which table.

    String userNamePart = null;
    if (userName.contains(":")) {
        int colonIndex = userName.lastIndexOf(":");
        userNamePart = userName.substring(0, colonIndex);
    }
    userNamePart = userNamePart == null ? userName : userNamePart;
    
    
    String userTypePart = null;
    if (userName.contains(":")) {
        int colonIndex = userName.lastIndexOf(":");
        userTypePart = userName.substring(colonIndex + 1, userName.length());
    }
    

Solution 2:[2]

At first customer is also user, isn't it? So maybe simpler solution would be to create customer also as user (use some flag/db field usertype ={USER|CUSTOMER|...}). If you still need to manage two entities, your approach is right, but In your DetailService just implement the another method which will read customer entity and then create spring's User.

Solution 3:[3]

I faced the same problem too! It gave a error message like following: AuthenticationManager This predefined class will help you to achieve this.

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

To over come this issue we should use Qualifier! Please go through the following link , it will guide you to use qualifier

This is my first answer give it a like!

https://developpaper.com/can-spring-security-dock-multiple-user-tables-at-the-same-time/

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Primary
    UserDetailsService us1() {
        return new InMemoryUserDetailsManager(User.builder().username("javaboy").password("{noop}123").roles("admin").build());
    }
    @Bean
    UserDetailsService us2() {
        return new InMemoryUserDetailsManager(User.builder().username("sang").password("{noop}123").roles("user").build());
    }
    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        DaoAuthenticationProvider dao1 = new DaoAuthenticationProvider();
        dao1.setUserDetailsService(us1());

        DaoAuthenticationProvider dao2 = new DaoAuthenticationProvider();
        dao2.setUserDetailsService(us2());

        ProviderManager manager = new ProviderManager(dao1, dao2);
        return manager;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/hello").hasRole("user")
                .antMatchers("/admin").hasRole("admin")
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

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 RobC
Solution 2 bilak
Solution 3