Lee Theobald
Lee Theobald

Reputation: 8587

Custom Authentication Provider Not Being Called

I'm trying to setup a customer AuthenticationProvider with Spring Security but not having much luck getting it working. I'm using Java configuration so I'm probably missing something simple but as most the learning material is XML config based, it's not jumping out at me.

This is using Spring v4.0.1.RELEASE but with Spring Security v3.2.2.RELEASE. Version number clash perhaps?

As far as I could tell, all I had to do was create my provider:

public class KBServicesAuthProvider implements AuthenticationProvider {
  @Autowired
  private ApplicationConfig applicationConfig;

  @Autowired
  private SessionServiceClient sessionServiceClient;

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    String email = (String) authentication.getPrincipal();
    String password = (String) authentication.getCredentials();

    try {
      KBSessionInfo sessionInfo = sessionServiceClient.login(applicationConfig.getKbServicesPresenceId(), email,
          password);

      List<GrantedAuthority> grantedRoles = new ArrayList<>();
      for (KBRoleMembership role : sessionInfo.getAuthenticatedUser().getRoleMemberships()) {
        grantedRoles.add(new SimpleGrantedAuthority(role.getRoleId()));
      }

      return new UsernamePasswordAuthenticationToken(email, password, grantedRoles);
    } catch (InvalidSessionException e) {
      throw new AuthenticationCredentialsNotFoundException("Username or password was not accepted", e);
    }
  }

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

And then setup a class to describe my security setup. This class links in my provider:

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired(required = true)
  SessionServiceClient sessionServiceClient;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated();
    http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
  }

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

  @Bean
  protected AuthenticationProvider getKBServicesAuthenticationProvider() {
    return new KBServicesAuthProvider();
  }
}

But I'm not seeing anything in the logs & none of my debug points are being hit. The app acts as it's unsecured (so I can reach various URLs etc. still).

Any ideas on what I should be checking?

Upvotes: 27

Views: 34070

Answers (10)

Nicolas Mafra
Nicolas Mafra

Reputation: 191

In my case, the problem was that I needed to disable CSRF, since I'm using API, not HTML pages:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
}

Upvotes: 0

Ricardo Gellman
Ricardo Gellman

Reputation: 1399

Also, make sure you are sending the user and password with correct headers.

Test curl below, and check the class is been invoked

curl -X GET \
  http://localhost:8080  \
  -H 'Authorization: Basic cmdlbGxtYW5AYnIuaWJtLmNvbTphYmM=' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'cache-control: no-cache'

Upvotes: 0

Mohit Sharma
Mohit Sharma

Reputation: 380

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()

Upvotes: 9

I had a similar problem, and this was because I used @Autowire-ed AuthenticationManager instance, that was built by spring boot and simply not contained my custom AuthenticationProvider.

After two days of debugging of spring guts I finally realize that this is not same instance, as manager from org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#authenticationManager() which I configured in my custom WebSecurityConfigurerAdapter through WebSecurityConfigurerAdapter#configure(AuthenticationManagerBuilder).

Now I simply get this instance from WebSecurityConfigurerAdapter#authenticationManager() and pass it to my GenericFilterBean, which is handle my auth logic. Works fine.

Upvotes: 0

Christopher Z
Christopher Z

Reputation: 952

You forgot the @Autowired annotation.

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

Also, you may want to remove the .antMatchers("/").permitAll().

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests().anyRequest().authenticated();
  http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
}

Upvotes: 1

Steve Park
Steve Park

Reputation: 2019

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

Upvotes: 3

Nitesh Virani
Nitesh Virani

Reputation: 1712

@EnableWebMvcSecurity will be deprecated in 4.0 https://jira.spring.io/browse/SEC-2790

You might want to re consider you configuration.

Upvotes: 1

Bal
Bal

Reputation: 2087

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.

Upvotes: 13

Nitin Prabhu
Nitin Prabhu

Reputation: 634

   Something like should be present in java config 
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class HelloMethodSecurityConfig {
}

Upvotes: 1

Nitin Prabhu
Nitin Prabhu

Reputation: 634

<security:global-method-security pre-post-annotations="enabled"/>

Upvotes: 0

Related Questions