Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security Multiple HTTPSecurity with Different User Details Services Not Working in Spring Boot

I have two types of users: Application User and End User and I have separate tables for these. Now, I want to apply security on these two tables.

I provided custom implementation of UserDetailsService for Application users:

@Component("applicationUserDetailsService")
public class ApplicationUserDetailsService implements UserDetailsService {}

And, I provided another second custom implementation of UserDetailsService for End users:

@Component("endUserDetailsService")
public class EndUserDetailsService implements UserDetailsService {}

Now, in the following code snippet, I have registered two endpoints for both type of users. I have injected both implementation of UserDetailsService and registered by @Overide configure(AuthenticationManagerBuilder auth) method for both application and end user separately.

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

// Injected via Constructor Injection
private final EndUserDetailsService endUserDetailsService;

private final ApplicationUserDetailsService applicationUserDetailsService;

@Configuration
@Order(1)
public class ApplicationUserSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
            .and()
            .headers()
            .frameOptions()
            .disable()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .antMatcher("/api/customer/**")
            .authorizeRequests()
            .antMatchers("/api/customer/authenticate").permitAll()
            .antMatchers("/api/customer/**")
            .authenticated()
            .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    }

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

//no @Order defaults to last
@Configuration
public class EndUserSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
            .and()
            .headers()
            .frameOptions()
            .disable()
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/api/authenticate").permitAll()
            .antMatchers("/api/**").authenticated()                               
            .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    }

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

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

And, I'm trying to authenticate the user like this:

//Injected via Constructor Injection
private final AuthenticationManagerBuilder authenticationManagerBuilder;

UsernamePasswordAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);

When the above code snippet is executed, I get the Null Pointer Exception because authenticationManagerBuilder.getObject() returns NULL. And when I use just when implementation of UserDetailService with @Component("userDetailsService") and not set UserDetailService in security config like auth.userDetailsService("..."), it works fine but by that way I can't achieve authentication from multiple tables.

What I want to Achieve: In simple words, I want spring security to authenticate user from two tables.

like image 760
Bilal Ahmed Yaseen Avatar asked Aug 28 '19 07:08

Bilal Ahmed Yaseen


1 Answers

requestMatchers() is the call that you need as it allows you to isolate adapters by URL:

@Order(1)
@EnableWebSecurity
class EndUserConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .requestMatchers()
                .antMatchers("/api/customer/**")
                .and()
            .authorizeRequests()
                .antMatchers("/**").hasRole("CUSTOMER")
                .and()
            .apply(yourJointConfigurations());
    }

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

Regarding calling the AuthenticationManager directly, it would be ideal if you could rely on the existing filter chain to do the work for you. For example, since you are stateless, HTTP Basic might be a better fit for you, which you could apply to both configurations, instead of trying to have a dedicated /authenticate endpoint.

like image 191
jzheaux Avatar answered Oct 22 '22 10:10

jzheaux