9

I have 3 different tables and every table has user-information. (Maybe the same username but different passwords)

Also, have 3 different URLs for authorization. Is it possible to use multiple UserDetailsService with one configuration and during authorization control which table to use?

Here is my configuration code but I can't control which table to use during authorization:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    @Qualifier("userDetailsService")
    private final UserDetailsService userDetailsService;

    @Qualifier("customerDetailsService")
    private final UserDetailsService customerDetailsService;

    private final TokenProvider tokenProvider;

    private final CorsFilter corsFilter;

    private final SecurityProblemSupport problemSupport;

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

    @PostConstruct
    public void init() {
        try {
            authenticationManagerBuilder
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder())
                .and()
                .userDetailsService(customerDetailsService)
                .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")
            .antMatchers("/test/**");
    }

    @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/authenticate").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);
    }
}

userDetailsService and customerDetailsService are my UserDetailsService implementations that use different tables for check credential. But I can't control exactly which UserDetailsService to use when a request came.

IARI
  • 1,217
  • 1
  • 18
  • 35
pronomy
  • 204
  • 4
  • 15
  • 2
    Have you tried having to have only one service that would compose userDetailsService and customerDetailsService ? – Gaël Marziou Oct 12 '18 at 17:22
  • Take a look (https://spring.io/guides/tutorials/spring-security-and-angular-js/) `Gateway` pattern, it's about a number of separated authorization endpoints. – WildDev Oct 12 '18 at 19:31
  • how did you achieve it? Can you please share your approach? – Bilal Ahmed Yaseen Aug 27 '19 at 13:49
  • @GaëlMarziou What do you mean by composing two services under one? How, Spring security will identify that which service to use? – Bilal Ahmed Yaseen Aug 28 '19 at 07:26
  • Possible duplicate of [Multiple user details services for different endpoints](https://stackoverflow.com/questions/49450556/multiple-user-details-services-for-different-endpoints) – Eleftheria Stein-Kousathana Aug 28 '19 at 13:37
  • @BilalAhmedYaseen added special words at the end of the username when I get request and catch it on UserDetailService. based on this value creating query in different DB. But this is not a good solution. Now I am using keycloak. – pronomy Aug 30 '19 at 06:14
  • @BilalAhmedYaseen I'm only guessing, but @GaëlMarziou proposed a solution in which you create your own class implementing `UserDetailsService` interface and in its `loadUserByUsername` method you traverse all your actual User Detail Services injected as beans. Using this solution, you can have hundreds of different details services traversed in any way you want. – itachi Dec 19 '19 at 15:35
  • 1
    Spring Security can handle multiple providers, it will query each, until one resolves the credentials, returns error when the list is exhausted. You can just set as many UserDetailsServices you need. There is a simple example here as well: https://www.baeldung.com/spring-security-multiple-auth-providers – Olix Dec 22 '21 at 10:38
  • I don't think you have a Spring Security problem, it looks like a Spring java-style conf problem. You should have your multiple "UserDetailsService" declared in your conf as @Beans, then constructor-injected, not declared as empty attributes in your SecurityConfiguration – Tristan Sep 21 '22 at 08:02

2 Answers2

0

You can use this article https://sanketdaru.com/blog/multiple-sources-user-details-spring-security/ . It has example in which it defines two services in service and use that single service. Just like my code of user detail service.

@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
    List<UserEntity> users = userRepository.findByName(name);
    if (users.isEmpty()){
        return inMemoryUserDetailsService.loadUserByUsername(name);
    }
    return new UserDetailEntity (users.get(0));
}

@PostConstruct
public void init() {
    this.inMemoryUserDetailsService = initInMemoryUserDetailsService();
}

private UserDetailsService initInMemoryUserDetailsService() {
    List<UserDetails> userDetails = new ArrayList<>();
    UserDetails userDetails1 = new User("user1", "$2a$10$t/U97dFDQ0e8ujCq6728P.E1axs/aoAMsopoSUQtTchiKTP/Ps4um", Collections.singletonList(new SimpleGrantedAuthority("USER")));
    UserDetails userDetails2 = new User("admin1", "$2a$10$t/U97dFDQ0e8ujCq6728P.E1axs/aoAMsopoSUQtTchiKTP/Ps4um", Arrays.asList(new SimpleGrantedAuthority("USER"),new SimpleGrantedAuthority("ADMIN")));
    userDetails.add(userDetails1);
    userDetails.add(userDetails2);
    return new InMemoryUserDetailsManager(userDetails);
}
NandaniK
  • 1
  • 2
-1

Please try it in WebSecurityConfigurerAdapter

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(yourCustomUserDetailsService).passwordEncoder(passwordEncoder);
}