Reputation: 2835
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
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
@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
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