Custom Authentication Provider Not Being Called

This might not be the complete answer, as I'm struggling with this a bit myself. I'm using a custom authentication provider and a custom user details service. I see the same behavior as you -- breakpoints get hit in my user details service, but not in my authentication provider. Here is what my entire config class looks like:

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;
    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        AuthenticationProvider rememberMeAuthenticationProvider = rememberMeAuthenticationProvider();
        TokenBasedRememberMeServices tokenBasedRememberMeServices = tokenBasedRememberMeServices();

        List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(2);
        authenticationProviders.add(rememberMeAuthenticationProvider);
        authenticationProviders.add(customAuthenticationProvider);
        AuthenticationManager authenticationManager = authenticationManager(authenticationProviders);

        http
                .csrf().disable()
                .headers().disable()
                .addFilter(new RememberMeAuthenticationFilter(authenticationManager, tokenBasedRememberMeServices))
                .rememberMe().rememberMeServices(tokenBasedRememberMeServices)
                .and()
                .authorizeRequests()
                .antMatchers("/js/**", "/css/**", "/img/**", "/login", "/processLogin").permitAll()
                .antMatchers("/index.jsp", "/index.html", "/index").hasRole("USER")
                .antMatchers("/admin", "/admin.html", "/admin.jsp", "/js/saic/jswe/admin/**").hasRole("ADMIN")
                .and()
                .formLogin().loginProcessingUrl("/processLogin").loginPage("/login").usernameParameter("username").passwordParameter("password").permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/login")
                .and()
                .logout().permitAll();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/img/**");
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(List<AuthenticationProvider> authenticationProviders) {
        return new ProviderManager(authenticationProviders);
    }

    @Bean
    public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
        return new TokenBasedRememberMeServices("testKey", userDetailsService);
    }

    @Bean
    public AuthenticationProvider rememberMeAuthenticationProvider() {
        return new org.springframework.security.authentication.RememberMeAuthenticationProvider("testKey");
    }

    protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

I've just discovered that if I specifically add my authentication provider to the HttpSecurity object, my breakpoints start getting hit:

http
                .csrf().disable()
                .headers().disable()
                .authenticationProvider(customAuthenticationProvider)

My goal is to get a BCryptPasswordEncoder working, which does not with this config -- everything returns as bad credentials. Anyway, just thought I'd share.


Use isAssignableFrom() instead of == or equals.

The problem is with the supports() method which will always return false.

Change from:

@Override
public boolean supports(Class<?> authentication) {
      return authentication.equals(UsernamePasswordAuthenticationToken.class);
}

To:

Java

@Override
public boolean supports(Class<?> authentication) {
    return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

Kotlin

override fun supports(authentication: Class<*>): Boolean {
    return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)
}

Finally the flow would pass through authenticate()


I had same issue (my custom auth provider is not hit) and solved the issue by introducing springSecurityFilterChain, after reading Why is Spring Security working in Tomcat but not when deployed to Weblogic? So my problem was maybe tied specifically with WebServer, but I had the custom auth provider issue also on Tomcat and checked my configuration works now on Tomcat.

I'm using spring boot 1.4.1 version which contains Spring 4.3.3 and Spring Security 4.1.3 and following Traditional deployment

I tested my configuration against Tomcat v9.0 and also WebLogic 12c R2 and checked it worked on both. hope this helpful at least to someone using Tomcat.

Below is my configuration started from main class.

Application.java

public class Application {
    public static void main( String[] args ) {
        SpringApplication.run(new Class[] {AppConfig.class, Initializer.class, SecurityInitializer.class}, args);
    }
}

Initializer.java

public class Initializer extends SpringBootServletInitializer implements WebApplicationInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(AppConfig.class);
    }

    @Override
    public void onStartup(ServletContext container) throws ServletException {
        AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
        rootContext.register(AppConfig.class);

        // Manage the lifecycle of the root application context
        container.addListener(new ContextLoaderListener(rootContext));

        // Create the dispatcher servlet's Spring application context
        AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
        dispatcherContext.register(WebConfig.class);

        // Register and map the dispatcher servlet
        ServletRegistration.Dynamic dispatcher = container.addServlet("my-servlet", new DispatcherServlet(dispatcherContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/*");
    }
}

Here the AbstractSecurityWebApplicationInitializer is building the springSecurityFilterChain from onStartup method. I didn't implement any, since I'm trying to use default configuration.

SecurityInitializer.java

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

AppConfig.java

@Configuration
@EnableAutoConfiguration
@EnableScheduling
@EnableMBeanExport
@EnableAsync
@EnableAspectJAutoProxy
@ComponentScan("com.my.package")
public class AppConfig {


}

SecurityConfig.java

@Configuration
@EnableWebSecurity
@ComponentScan("com.my.package")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RestfulRemoteAuthenticationProvider restfulRemoteAuthenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(restfulRemoteAuthenticationProvider);
    }

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

WebConfig.java

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.my.controller.package")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public InternalResourceViewResolver internalViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setOrder(1);
        return viewResolver;
    }
}

This is my custom auth provider to get authentication info from other component via Restful request

RestfulRemoteAuthenticationProvider.java

@Component
public class RestfulRemoteAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private ManagementClientAdapterFactory managementClientAdapterFactory;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // my logic to get and configure authSource which is my environment specific thing, also same for RemoteAuthRequestResult

        RemoteAuthRequestResult result = (RemoteAuthRequestResult)authSource.sendRequest();
        if(result.isAuthenticated()) {
            List<GrantedAuthority> grantedAuths = new ArrayList<>();
            grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
        }
        throw new BadCredentialsException("User not found by given credential");
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}