Shankar
Shankar

Reputation: 2835

Spring Security ProviderNotFoundException when user authentication fails

I have setup a LDAP Custom Authentication provider similar to the example here - https://www.baeldung.com/spring-security-authentication-provider

There is a Login Controller to handle login errors and to check if the user is in an approved list. The Controller calls the Custom authentication provider, the authenticationManager.authenticate() method.

When wrong credentials are provided, the Custom Auth provider is called twice. Two exceptions are thrown.

First Exception:

31-12-2020 15:42:55.577 [http-nio-9090-exec-6] ERROR c.c.t.a.CustomAuthenticationProvider.hasAccess - test is not authenticated
javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C09044E, comment: AcceptSecurityContext error, data 52e, v2580 ]
        at com.sun.jndi.ldap.LdapCtx.mapErrorCode(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.processReturnCode(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.connect(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.ensureOpen(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.ensureOpen(Unknown Source) ~[na:1.8.0_261]
        at com.sun.jndi.ldap.LdapCtx.reconnect(Unknown Source) ~[na:1.8.0_261]
        at javax.naming.ldap.InitialLdapContext.reconnect(Unknown Source) ~[na:1.8.0_261]
        at com.tools.auth.CustomAuthenticationProvider.hasAccess(CustomAuthenticationProvider.java:65) [classes!/:1.0.0-SNAPSHOT]
        at com.tools.auth.CustomAuthenticationProvider.authenticate(CustomAuthenticationProvider.java:32) [classes!/:1.0.0-SNAPSHOT]
        at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) [spring-security-core-5.0.3.RELEASE.jar!/:5.0.3.RELEASE]
        at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:199) [spring-security-core-5.0.3.RELEASE.jar!/:5.0.3.RELEASE]
        at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:502) [spring-security-config-5.0.3.RELEASE.jar!/:5.0.3.RELEASE]
        at com.tools.web.JwtAuthenticationRestController.authenticate(JwtAuthenticationRestController.java:70) [classes!/:1.0.0-SNAPSHOT]

Second Exception:

c.c.t.a.CustomAuthenticationProvider.authenticate - User does not have access
31-12-2020 15:42:55.613 [http-nio-9090-exec-6] ERROR c.c.t.w.JwtAuthenticationRestController.authenticate - Exception logging in user
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
        at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:227) ~[spring-security-core-5.0.3.RELEASE.jar!/:5.0.3.RELEASE]
        at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:502) ~[spring-security-config-5.0.3.RELEASE.jar!/:5.0.3.RELEASE]
        at com.tools.web.JwtAuthenticationRestController.authenticate(JwtAuthenticationRestController.java:70) [classes!/:1.0.0-SNAPSHOT]

This is the Custom provider:

public class CustomAuthenticationProvider implements AuthenticationProvider {

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

        if (hasAccess(name, password)) {
            Authentication auth = new UsernamePasswordAuthenticationToken(name,
                    password);
            return auth;
        } else {
            return null;
        }
    }

    public boolean supports(Class<?> authentication) {
        return true;
    }

    public boolean hasAccess(final String username, final String password) {
    //LDAP access happens here
    }
}

This is the Controller:

public class JwtAuthenticationRestController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @CrossOrigin
    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest)
            throws AuthenticationException {
        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
         //generate token
        return ResponseEntity.ok(new JwtTokenResponse(token));
    }

    @ExceptionHandler({AuthenticationException.class})
    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
    //handle exception. set custom response.
    }

    private void authenticate(String username, String password) {   
        try {
            // Check against the approved user list
            //Authenticate the user - Exception thrown here
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }catch (Exception e) {
            throw new AuthenticationException("APPLICATION_ERROR", e);
        }
    }
}

Update Here is the Web Security configuration:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;

    @Autowired
    private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new CustomAuthenticationProvider());
    }   

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .csrf().disable()
            .exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint).and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeRequests()
            .anyRequest().authenticated();

        httpSecurity
                .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        httpSecurity
            .headers()
            .frameOptions().sameOrigin()  
            .cacheControl(); 
    }

    @Override
    public void configure(WebSecurity webSecurity)  {
        webSecurity
            .ignoring()
            .antMatchers(
                HttpMethod.POST,
                "/authenticate" //authentication path
            )
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .and()
            .ignoring()
            .antMatchers(
                HttpMethod.GET,
                        "\"login" //Ignore security for Login page.
                )
            .and()
            .ignoring()
            .antMatchers("/h2-console/**/**");    
    }

This happens only when authentication fails due to invalid password. I have checked that the Custom provider throws javax.naming.AuthenticationException and returns null for invalis credentials.

Why does Spring throw this exception for failed authentication? The work around is to handle exception in the Controller as a login failure, but still it would be good to understand why this happens.

Upvotes: 3

Views: 7196

Answers (2)

Kumar Ashutosh
Kumar Ashutosh

Reputation: 1254

Just implementing from AuthenticationProvider would not solve your purpose. You need to register your provider with AuthenticationManagerBuilder. Hope you have not missed that step on Register the Auth Provider

You could do something like this:


@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

private final CustomAuthenticationProvider customAuthenticationProvider;

@Autowired
public MyWebSecurityConfig(CustomAuthenticationProvider customAuthenticationProvider){
 this.customAuthenticationProvider = customAuthenticationProvider;
}

@Override
public void configure(AuthenticationManagerBuilder authBuilder) throws Exception {
  authBuilder.authenticationProvider(CustomAuthenticationProvider );
}

Inject your custom-provide in your WebSecurityConfig class and set that field as one of the auth provider.

Upvotes: 1

Eric
Eric

Reputation: 155

Have you verified the CustomAuthenticationProvider is being invoked from within your controller? That exception pretty clearly came from the ProviderManager class. From the ProviderManager javadoc:

If no provider returns a non-null response, or indicates it can even process an Authentication, the ProviderManager will throw a ProviderNotFoundException.

If you've followed the Baeldung example, spring is handling the auth process much further up in the call stack than your controller.

Upvotes: 3

Related Questions