Multiple user details services for different endpoints
Asked Answered
S

3

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?

Stepdaughter answered 23/3, 2018 at 13:16 Comment(0)
A
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.

Animato answered 23/3, 2018 at 15:42 Comment(10)
Can you please have a look into this question: #57687145 I'm having the same problem and specifying different userDetailService in each spring security config not working for me. ThanksAnticlinal
Thanks man i got it... I've shared it hereFirstly
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?Chronologist
@surajbahl, happy to help, but it sounds like you should open up your own questionAnimato
@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?Chronologist
@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.Animato
@FreddyDaniel I am kind of stuck in the same situation and can't figure it out. #65711647 Do you mind taking a look please?Boggart
@Animato I am kind of stuck in the same situation and can't figure it out. #65711647. Do you mind taking a look please?Boggart
@Animato this will impact performance, I would really advise to use one userDetailsService and map them one by oneTeuton
Thanks, @MahmoudOdeh, I added an alternative solution based on your feedback. Since the question was how to support two UserDetailsServices, 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 UserDetailsServices are unnecessary.Animato
C
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();
    }

  }
}
Chronologist answered 10/3, 2020 at 1:46 Comment(3)
Were you able to solve this? I am kinda stuck in the same situation.Boggart
me too. Stuck for hours :(Contradance
did anyone solve this, I am having the same problemHygrometry
K
0

The ProviderManager implementation of Authentication iterates through all the Authentication providers while authenticating and checks whether the specific provider supports the specific token; if yes, then it delegates the request to that provider.

So what we can do is create two subclasses of the UsernamePasswordAuthentication token for each Entity (in the example, for the seller and the customer). And also subclass the DaoAuthenticationProvider for the seller and the customer, and each provider would support the token of their respective entity. We would simply delegate the request to the superclass for authenticating since the tokens are subclasses of UsernamePasswordAuthentication.

On logging in as the seller, we can create the SellerAuthenticationToken, and for the customer we can create a token for CustomerAuthenticationToken, and while authenticating the ProviderManager would delegate it to the correct provider.

Also, you need to implement two UserDetailsService and set the userdetails correctly for the providers.

All of the configurations and sample code is given below.

@Configuration
public class SecurityConfig{
@Bean
public CustomerAuthenticationProvider customerAuthenticationProvider(@Qualifier("customerUserDetailsService")
                                                                         UserDetailsService userDetailsService){
    CustomerAuthenticationProvider customerAuthenticationProvider = new CustomerAuthenticationProvider();
    customerAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    customerAuthenticationProvider.setUserDetailsService(userDetailsService);

    return customerAuthenticationProvider;
}
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Bean
    public SellerAuthenticationProvider sellerAuthenticationProvider(@Qualifier("sellerUserDetailsService")
                                                                     UserDetailsService userDetailsService
                                                                     ){
        SellerAuthenticationProvider sellerAuthenticationProvider = new SellerAuthenticationProvider();
        sellerAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        sellerAuthenticationProvider.setUserDetailsService(userDetailsService);
        return sellerAuthenticationProvider;
    }
    @Bean
    public AuthenticationManager authenticationManager(SellerAuthenticationProvider sellerAuthenticationProvider,
                                                       CustomerAuthenticationProvider customerAuthenticationProvider){

        return new ProviderManager(sellerAuthenticationProvider, customerAuthenticationProvider);
    }

}

public class SellerAuthenticationToken extends UsernamePasswordAuthenticationToken {
    public SellerAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public SellerAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

public class CustomerAuthenticationToken extends UsernamePasswordAuthenticationToken {
    public CustomerAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public CustomerAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

public class CustomerAuthenticationProvider extends DaoAuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if(!(authentication instanceof CustomerAuthenticationToken)){
            throw new IllegalArgumentException("invalid");
        }
        return super.authenticate(authentication);
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return (CustomerAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

public class SellerAuthenticationProvider extends DaoAuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException{
        if(!(authentication instanceof SellerAuthenticationToken)){
            throw new IllegalArgumentException("invalid");
        }
        return super.authenticate(authentication);
    }
    @Override
    public boolean supports(Class<?> authentication) {
        return (SellerAuthenticationToken.class.isAssignableFrom(authentication));
}
}
 //While logging in from the seller login endpoint
 Authentication authentication = authenticationManager.authenticate(
            new SellerAuthenticationToken(request.username(), request.password())
    );
//While logging the customer from the customer endpoint
Authentication authentication = authenticationManager.authenticate(
                new CustomerAuthenticationToken(request.username(), request.password())
        );
Krever answered 9/9 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.