Currently I'm using ActiveDirectoryLdapAuthenticationProvider
and one custom provider on security authentication process. Assuming that CustomAuthenticationProvider
throws exception, security flow goes to ldapAuthenticationProvider
.
I have a problem that when there is no connection to AD server I get CommunicationException
, which can't be handled:
a) by @ControllerAdvice
and @ExceptionHandler
b) by AuthenticationFailureHandler
c) by AuthenticationEntryPoint
I got exception in console and callback for default spring "error". I'm assuming it is done somewhere in the background, but I can't do anything about it and find the cause. I want to catch CommunicationException
and throw my CustomException
(or any other) to resolve message on javascript client. How can I do that?
SecurityConfig:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.and().authorizeRequests()
.antMatchers("...").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login")
.failureHandler(new CustomFailureHandler())
.successHandler(new CustomSuccessHandler()).permitAll()
.and().logout().permitAll()
.and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth
.authenticationProvider(new CustomAuthenticationProvider())
.authenticationProvider(ldapAuthenticationProvider());
}
UnauthorizedHandler:
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
String[] url = request.getRequestURL().toString().split(request.getContextPath());
String redirect = request.getContextPath();
if (url.length == 2) redirect = request.getContextPath() + "/?callbackUrl=" + url[1].substring(1);
response.sendRedirect(redirectUrl);
}
}
CustomFailureHandler:
@Component
public class CustomFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException exception) throws IOException {
String message = "...";
if (exception instanceof BadCredentialsException) {
message = "...";
} else if (exception instanceof CommunicationException) {
message = "...";
}
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setStatus(500);
httpServletResponse.getWriter().print(message);
httpServletResponse.getWriter().flush();
httpServletResponse.getWriter().close();
}
}
Exception:
org.springframework.ldap.CommunicationException: 10.1.3.13:389; nested exception is javax.naming.CommunicationException: 10.1.3.13:389 [Root exception is java.net.ConnectException: Connection timed out: connect]
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:108)
at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.bindAsUser(ActiveDirectoryLdapAuthenticationProvider.java:215)
at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.doAuthentication(ActiveDirectoryLdapAuthenticationProvider.java:146)
at org.springframework.security.ldap.authentication.AbstractLdapAuthenticationProvider.authenticate(AbstractLdapAuthenticationProvider.java:85)
at org........authenticate(ActiveDirectoryLdapAuthenticationProvider.java:29)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:200)
at org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:124)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:835)
Caused by: javax.naming.CommunicationException: 10.1.3.13:389
at java.naming/com.sun.jndi.ldap.Connection.<init>(Connection.java:237)
at java.naming/com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137)
at java.naming/com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1610)
at java.naming/com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2752)
at java.naming/com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:320)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxFromUrl(LdapCtxFactory.java:225)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:189)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:243)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:154)
at java.naming/com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:84)
at java.naming/javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:730)
at java.naming/javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305)
at java.naming/javax.naming.InitialContext.init(InitialContext.java:236)
at java.naming/javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:154)
at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider$ContextFactory.createContext(ActiveDirectoryLdapAuthenticationProvider.java:426)
at org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider.bindAsUser(ActiveDirectoryLdapAuthenticationProvider.java:206)
... 47 common frames omitted
Caused by: java.net.ConnectException: Connection timed out: connect
at java.base/java.net.PlainSocketImpl.connect0(Native Method)
at java.base/java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:101)
at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399)
at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:242)
at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:224)
at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:403)
at java.base/java.net.Socket.connect(Socket.java:591)
at java.base/java.net.Socket.connect(Socket.java:540)
at java.base/java.net.Socket.<init>(Socket.java:436)
at java.base/java.net.Socket.<init>(Socket.java:213)
at java.naming/com.sun.jndi.ldap.Connection.createSocket(Connection.java:330)
at java.naming/com.sun.jndi.ldap.Connection.<init>(Connection.java:216)
... 62 common frames omitted
CommunicationException
is not an instance of AuthenticationException
so the line
if (exception instanceof CommunicationException) {
will never pass.
A simple way to catch this exception would be to wrap the LDAP authentication provider in your own:
public class MyLdapAuthenticationProvider implements AuthenticationProvider {
private final ActiveDirectoryLdapAuthenticationProvider delegate;
public MyLdapAuthenticationProvider(
ActiveDirectoryLdapAuthenticationProvider delegate) {
this.delegate = delegate;
}
public Authentication authenticate(Authentication authentication) {
try {
return this.delegate.authenticate(authentication);
} catch (CommunicationException e) {
throw new InternalAuthenticationServiceException(e);
}
}
}
And then:
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth
.authenticationProvider(new CustomAuthenticationProvider())
.authenticationProvider(
new MyLdapAuthenticationProvider(ldapAuthenticationProvider()));
}
Now that said, there is an open ticket in Spring Security so that CommunicationException
is correctly wrapped in an InternalAuthenticationServiceException
. You might consider contributing a fix so that you don't need the delegating authentication provider.