2

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.

Shankar
  • 2,625
  • 3
  • 25
  • 49
  • 1
    Can you provide also your Spring Security configuration ?? Have you added your AuthenticationProvider to the authenticationManager ?? – Juan BC Jan 01 '21 at 09:12
  • @JuanBC The Auth Provider is added in the security configuration. I added the security configuration to the question as well. – Shankar Jan 04 '21 at 18:35

2 Answers2

3

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.

Eric
  • 155
  • 2
  • 8
  • The CustomAuthenticationProvider is invoked in the Controller for both cases (valid/invalid credentials), based on the logs. – Shankar Jan 04 '21 at 18:49
  • What logs though? I don't see any logging in your classes. The partial stacktrace you posted clearly shows the exception originated in at `ProviderManager.authenticate(ProviderManager.java:227)` That class is a part of spring security. If it's being invoked then it's happening before your controller is ever reached. – Eric Jan 04 '21 at 20:17
1

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.

Kumar Ashutosh
  • 1,121
  • 10
  • 33