I am implementing custom 'AuthenticationProvider'. If not authenticated I am throwing exception inside 'authenticate' function as shown below.
public class DelegatingLdapAuthenticationProvider implements AuthenticationProvider {
private ActiveDirectoryLdapAuthenticationProvider primaryProvider;
private List<ActiveDirectoryLdapAuthenticationProvider> secondaryProviders = new ArrayList<>();
public DelegatingLdapAuthenticationProvider() {
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Authentication result = null;
AuthenticationException exception = null;
try {
result = primaryProvider.authenticate(authentication);
} catch (AuthenticationException e) {
exception = e;
for (ActiveDirectoryLdapAuthenticationProvider secondaryProvider : secondaryProviders) {
try {
result = secondaryProvider.authenticate(authentication);
if (result.isAuthenticated()) {
break;
}
} catch (AuthenticationException e1) {
exception = e;
}
}
}
if (result == null || !result.isAuthenticated()) {
throw exception;
}
return result;
}
I have global exception handler as shown below.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({NoPermissionException.class})
@ResponseBody
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public Map<String, String> noPermission(NoPermissionException e) {
return createErrorResponse(e, "Don't have permissions");
}
@ExceptionHandler({Exception.class})
@ResponseBody
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String, String> exceptionInProcessing(Exception e) {
return createErrorResponse(e, "Unable to process. Unknown error occurred: " + e.getMessage());
}
private Map<String, String> createErrorResponse(Exception e, String errorMessage) {
Map<String, String> errorResponse = new HashMap<>();
errorResponse.put("message", errorMessage);
errorResponse.put("reason", e.toString());
return errorResponse;
}
}
When exception is thrown inside the 'authenticate' function, global exception handler is not being called. For all the other exceptions it is being called. I want to catch the exception inside global exception handler and return custom error message. How can I do that? Any help appreciated. Thanks in advance.
The Authentication Manager is only a interface and actual implementation of the authenticate method is provided by the ProviderManager. The ProviderManager has a list of AuthenticationProviders. From it's authenticate method it calls the authenticate method of the appropriate AuthenticateProvider.
To handle these exceptions at a global level via @ExceptionHandler and @ControllerAdvice, we need a custom implementation of AuthenticationEntryPoint. AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client.
Authentication providers are responsible to perform a specific authentication. Spring security provides several AuthenticationProvider . Remember these AuthenticationProviders can't execute directly, but spring security uses ProviderManager class which delegates to a list of configured authentication providers.
To plug in the new implementation of the AuthenticationProvider, override the configure(AuthenticationManagerBuilder auth) method of the WebSecurityConfigurerAdapter class in the configuration class. To register the multiple AuthenticationProvider implementations, we can invoke the auth.
The GlobalExceptionHandler
is for controller exception handler, but the AuthenticationProvider
is still in filter, if you want to handler the AuthenticationException
, you need to handle it to implement AuthenticationEntryPoint
and override the commence
method.
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException
AuthenticationException
and AccessDeniedException
have already been handled by ExceptionTranslationFilter
. You just need to inject AuthenticationEntryPoint
and AccessDeniedHandler
(which handle AccessDeniedException
)
Or you can catch these exception in filter and then handle it in filer, like AuthenticationFailureHandler
in AbstractAuthenticationProcessingFilter
To complement the @chaoluo answer:
AuthenticationEntryPoint
interface and resolve the exception by HandlerExceptionResolver
:@Component("restAuthenticationEntryPoint")
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint, AccessDeniedHandler {
@Autowired
private HandlerExceptionResolver resolver;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
resolver.resolveException(request, response, null, exception);
}
}
RestAuthenticationEntryPoint
into your WebSecurityConfigurerAdapter
implementation and use it as the authenticationEntryPoint:@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RestAuthenticationEntryPoint authenticationEntryPoint;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
HandlerExceptionResolver
, we can use the typical Spring Web error handling using @ControllerAdvice
and @ExceptionHandler
annotations:@RestControllerAdvice
public abstract class ErrorsControllerAdvice {
@ExceptionHandler
public ResponseEntity<?> handleException(Throwable exception, WebRequest webRequest, Locale locale) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With