4

I am building a REST API using Spring and am currently authenticating all my requests using a custom user details service and this configuration code:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}

I am also setting up a DaoAuthenticationProvider to use the my user details service and using that to configure global security.

Now, I want to provide an endpoint that (while still secured with HTTP basic authentication) uses a different user details service to check whether the user is allowed to access the given resource.

How do I use two different user details services for different endpoints?

rodalfus
  • 166
  • 2
  • 14

2 Answers2

10

One thing you can do is have two WebSecurityConfigurerAdapters:

@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
class FirstEndpointConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/specialendpoint")
                .and()
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.userDetailsService(/* first of your userDetailsServices */);
    }
}


@Configuration
class SecondEndpointConfiguration extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http // all other requests handled here
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.userDetailsService(/* second of your userDetailsServices */);
    }
}

requestMatchers() exists for targeting springSecurityFilterChains to specific endpoints.

EDIT: Mahmoud Odeh makes a good point that if the user bases are the same, then you may not need multiple UserDetailsService instances. Instead, you can use one change that isolates your special endpoint by an authority on the user's account:

http
    .authorizeRequests()
        .antMatchers("/specialendpoint").hasAuthority("SPECIAL")
        .anyRequest().authenticated()
        .and()
    .httpBasic();

Then, your single UserDetailsService would look up all users. It would include the SPECIAL GrantedAuthority in the UserDetails instance for users who have access to /specialendpoint.

jzheaux
  • 7,042
  • 3
  • 22
  • 36
  • Can you please have a look into this question: https://stackoverflow.com/questions/57686645/spring-security-multiple-httpsecurity-with-different-user-details-services-not-w I'm having the same problem and specifying different userDetailService in each spring security config not working for me. Thanks – Bilal Ahmed Yaseen Aug 28 '19 at 07:28
  • 2
    Thanks man i got it... I've shared it [here](https://github.com/freddy-daniel/spring-boot-server-multiple-login-rules) – Freddy Daniel Feb 11 '20 at 05:16
  • i tried implementing multiple userDetailService but spring is going into both services and not able to load user. Even if it finds the user in one service still fails. Can someone please tell me how to use different userDetailService for different endpoints? – suraj bahl Mar 06 '20 at 20:59
  • 1
    @surajbahl, happy to help, but it sounds like you should open up your own question – jzheaux Mar 07 '20 at 17:48
  • @jzheaux: I have put my code in answer below. Can you please take a look and tell me why is not going in different user service based on URL? – suraj bahl Mar 10 '20 at 01:44
  • 1
    @surajbahl, I'd recommend you start a new question. Asking your own distinct question inside of another person's question, especially as an answer, can be very confusing to future visitor's to this question. – jzheaux Mar 10 '20 at 21:51
  • @FreddyDaniel I am kind of stuck in the same situation and can't figure it out. https://stackoverflow.com/questions/65711147/authenticating-jwt-for-multiple-users-in-spring-boot Do you mind taking a look please? – A.J Jan 14 '21 at 00:24
  • @jzheaux I am kind of stuck in the same situation and can't figure it out. https://stackoverflow.com/questions/65711147/authenticating-jwt-for-multiple-users-in-spring-boot. Do you mind taking a look please? – A.J Jan 14 '21 at 00:25
  • @jzheaux this will impact performance, I would really advise to use one userDetailsService and map them one by one – Mahmoud Odeh Feb 06 '21 at 20:40
  • 1
    Thanks, @MahmoudOdeh, I added an alternative solution based on your feedback. Since the question was how to support two `UserDetailsService`s, I think it still makes sense to leave that as the first answer; however, as you said, there are a number of circumstances when the user base's overlap and thus separate `UserDetailsService`s are unnecessary. – jzheaux Feb 06 '21 at 21:55
2

I am trying to follow the solution given by M. Deinum but in my case it always goes to the same user service (v2userDetailsService) regardless of which URL is executed /v3/authorize/login or /v2/authorize/login. Here is my code:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration {


  @Configuration
  @Order(2)
  public static class V2Configuration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("v2userDetailsService")
    private UserDetailsService v2userDetailsService;

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

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
      ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
      auth
              .userDetailsService(v2userDetailsService)
              .passwordEncoder(passwordEncoder);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
              .frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
              .authorizeRequests()
              .antMatchers("/app").permitAll()
              .antMatchers("/v2/authorize/login").permitAll()
              .antMatchers("/v2/authorize/reLogin").permitAll()
              .antMatchers("/v2/authorize/logout").permitAll();
    }

  }



  @Configuration
  @Order(1)
  public static class V3Configuration extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("v3UserDetailsService")
    private UserDetailsService v3UserDetailsService;

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

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
      ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256);
      auth
              .userDetailsService(v3UserDetailsService)
              .passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and().csrf().disable().headers()
              .frameOptions().disable().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
              .authorizeRequests()
                          .antMatchers("/v3/authorize/login").permitAll()
                          .antMatchers("/v3/authorize/reLogin").permitAll()
                          .antMatchers("/v3/authorize/logout").permitAll();
    }

  }
}
suraj bahl
  • 2,864
  • 6
  • 31
  • 42