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);
}
}