Catching exception thrown in AuthenticationProvider - java

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 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:
Implement the 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);
}
}
Inject the 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);
}
}
Now because we resolved the exception by 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);
}
}

Authentication provider is called before controller exception handler has a chance to catch exceptions.
You can override AuthenticationFailureHandler to handle exceptions on security filter chain level, look at the examples
The behavior as described in documentation:
The filter calls the configured AuthenticationManager to process each authentication request. The destination following a successful authentication or an authentication failure is controlled by the AuthenticationSuccessHandler and AuthenticationFailureHandler strategy interfaces, respectively. The filter has properties which allow you to set these so you can customize the behaviour completely

As #chaoluo already said you need to implement AuthenticationEntryPoint and override the commence method. If you want to return an error JSON object you can do the following:
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//create errorObj
PrintWriter writer = response.getWriter();
mapper.writeValue(writer, errorObj);
writer.flush();
}

Related

Spring security: Failed authentication with more details

I'm trying to get the exact motive for a failed autentication (i.e wrong password, user doesn't exist, and so on) problem is, no matter how I try do simulate this actions I always get "Full authentication is required to access this resource", how can I get a more detailed exception ? TKS in advance !
Here's my code:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
UnauthorizedCustomResponse unauthorizedCustomResponse;
#Autowired
AccessDeniedCustomResponse accessDeniedCustomResponse;
#Autowired
CryptographyService cryptographyService;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(cryptographyService.getCryptography());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/signin", "/api/public/**", "/api/private/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedCustomResponse)
.accessDeniedHandler(accessDeniedCustomResponse)
.and()
.httpBasic();
}
}
#Component
public class AccessDeniedCustomResponse implements AccessDeniedHandler {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception)
throws IOException, ServletException {
logger.error("Usuário não autenticado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.CUSTOM_EXCEPTION_NOT_MAPPED, exception);
customException.flush(response);
}
}
#Component
public class UnauthorizedCustomResponse implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
logger.error("Acesso não autorizado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.ACCESS_DENIED, exception);
customException.flush(response);
}
}
You can analyze the specific exception that was thrown from the AuthenticationException superclass and act accordingly:
#Component
public class UnauthorizedCustomResponse implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger(this.getClass());
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
if(exception.getClass().isAssignableFrom(UsernameNotFound.class)) {
//Username not found
}
else if (exception.getClass().isAssignableFrom(BadCredentials.class)) {
//Bad credentials error
}
else if (exception.getClass().isAssignableFrom(AccountStatusException.class)) {
//Accound status exception
}
logger.error("Acesso não autorizado: {}", exception.getMessage());
CustomException customException = new CustomException(CustomExceptionMessage.ACCESS_DENIED, exception);
customException.flush(response);
}
}
You can find a better list of AuthenticationException known subclasses here: https://docs.spring.io/spring-security/site/docs/4.2.19.RELEASE/apidocs/org/springframework/security/core/AuthenticationException.html

Filter or handle AuthenticationException in OAuth2

I am trying to filter the AuthenticationException that is thrown during a user Authentication in my application. I know these cannot be filtered with #ControllerAdvice and #ExceptionHandler. So trying to figure out any Handler would work for my problem.
Already tried different approaches like AuthenticationFailureHandler but they didn't fit my requirement as I am using ResourceServerConfigurerAdapter.
Please suggest.
Spring security exceptions are handled by ExceptionTranslationFilter. You can create a custom filter that handles AuthenticationException and add it after ExceptionTranslationFilter. Default Spring security Filter Ordering.
public class AuthenticationExceptionFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (final Exception exception) {
if (exception instanceof AuthenticationException) {
this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
}
if(exception instanceof AccessDeniedException) {
....
}
// Check ExceptionTranslationFilter#handleSpringSecurityException(...)
}
You can register the filter programmatically by overriding the configure method of WebSecurityConfigurerAdapter.
#Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new AuthenticationExceptionFilter(), ExceptionTranslationFilter.class);
}
For Centralized exception handling across all #RequestMapping:
Check out ResponseEntityExceptionHandler
A convenient base class for #ControllerAdvice classes that wish to
provide centralized exception handling across all #RequestMapping
methods through #ExceptionHandler methods.
This base class provides an #ExceptionHandler method for handling
internal Spring MVC exceptions.
Here's a sample code snippet to get you started:
#ControllerAdvice
public class ExceptionHandler extends ResponseEntityExceptionHandler {
....
#ExceptionHandler({Exception.class})
public ResponseEntity<Object> handleCustomException(final CustomException exception, final WebRequest request) {
return handleExceptionInternal(exception,
ErrorOutputDto.create(exception.getErrorIdentifier(), exception.getMessage()),
new HttpHeaders(),
HttpStatus.UNAUTHORIZED,
request);
}
....

Returning custom error in spring security filter

I am working on a Spring Boot & Spring Security application that makes use of JSON Web Tokens.
I have a spring security filter that checks for the presence of an existing JWT and if so, injects a UsernamePasswordAuthenticationToken:
public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
#Value("${api.token.header}")
String tokenHeader;
#Autowired
TokenUtility tokenUtility;
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
String incomingToken = httpRequest.getHeader(tokenHeader);
if (SecurityContextHolder.getContext().getAuthentication() == null && incomingToken != null) {
UserDetails userDetails = null;
try {
userDetails = tokenUtility.validateToken(incomingToken);
} catch (TokenExpiredException e) {
throw new ServletException("Token has expired", e);
}
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
This filter is injected as follows:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
EntryPointUnauthorizedHandler unauthorizedHandler;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilter() throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManager());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// filter injected here
httpSecurity.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
If a user passes in a token that has expired, they receive the following error:
{
"timestamp":1496424964894,
"status":500,
"error":"Internal Server Error",
"exception":"com.app.exceptions.TokenExpiredException",
"message":"javax.servlet.ServletException: Token has expired",
"path":"/orders"
}
I know that spring security intercepts the requests before they make it to the controller layer, so I can't use my existing #ControllerAdvice to handle these exceptions.
My question is, how do I customise the error message/object that gets returned here? Elsewhere I use a JSON-serialized POJO to return error messages and I want to be consistent. I also don't want the user to see javax.servlet.ServletException
First, modify JWTTokenProvider Class to add a custom header to Http Servlet Request using setAttribute() method.
public boolean validateToken(String token,HttpServletRequest httpServletRequest){
try {
Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
return true;
}catch (SignatureException ex){
System.out.println("Invalid JWT Signature");
}catch (MalformedJwtException ex){
System.out.println("Invalid JWT token");
}catch (ExpiredJwtException ex){
System.out.println("Expired JWT token");
httpServletRequest.setAttribute("expired",ex.getMessage());
}catch (UnsupportedJwtException ex){
System.out.println("Unsupported JWT exception");
}catch (IllegalArgumentException ex){
System.out.println("Jwt claims string is empty");
}
return false;
}
Then modify commence method in JwtAuthenticationEntryPoint class to check expired header in http servlet request header that we added above.
#Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
final String expired = (String) httpServletRequest.getAttribute("expired");
System.out.println(expired);
if (expired!=null){
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,expired);
}else{
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Invalid Login details");
}
}
For more details see this Post. A nice simple solution.
As you are using .exceptionHandling() I believe you can configure a new ExceptionHandler;
Another way would be to override the messages you want to be different, like this post

Exception from GenericFilterBean are not catched with global exception handler #ControllerAdvice

I have a org.springframework.web.filter.GenericFilterBean filter. I would like to throw an exception when user is not authorized and catch this exception with #ControllerAdvice. But it seems that the handler doesn't do that.
Filter method
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
ClientAuthentication auth = (ClientAuthentication) authenticationService.getAuthentication(httpRequest, httpResponse);
SecurityContextHolder.getContext().setAuthentication(auth);
if (auth.isAuthenticated()) {
chain.doFilter(request, response);
} else {
ObjectMapper mapper = new ObjectMapper();
response.setCharacterEncoding("UTF-8");
httpResponse.getWriter().write(mapper.writeValueAsString(auth.getInfo()));
}
}
It works but a disadvantage is that I want to catch exception and render exception message back to the client with respect to Content-Type and Accept HTTP header. This solution renders what I want but into JSON only and my application has to handle XML and JSON.
Exception handler
it works when I throw exceptions from #Controller or #RestController but not from HTTP filter.
#ControllerAdvice
class GlobalExceptionHandler {
private static final Logger LOGGER = LogManager.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public
#ResponseBody
ResponseMessage badRequestException(BadRequestException ex) {
return ex.getResponseMessage();
}
/** rest omitted .... **/
}
Update #1
This is my Spring Security config where AuthFilter filter is set.
#EnableWebSecurity
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public static class ApiSecurity extends WebSecurityConfigurerAdapter {
private static Logger LOG = LogManager.getLogger(ApiSecurity.class);
#Bean
AuthenticationEntryPoint authenticationEntryPoint() {
LOG.debug("authenticationEntryPoint bean");
return new RestAuthenticationEntryPoint();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.servletApi()
.and()
.headers().cacheControl();
http.antMatcher(ApiController.API_ROOT + "/**").authorizeRequests().anyRequest().authenticated()
.and()
.addFilterBefore(new AuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(ApiController.API_ROOT + "/sandbox/**");
}
}
#Configuration
public static class WebFormSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService defaultUserService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(defaultUserService).passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll().and()
.logout().permitAll();
}
}
}

How to handle spring security InternalAuthenticationServiceException thrown in Spring ProviderManager

ProviderManager is throwing InternalAuthenticationServiceException.class while retrieving users in DaoAuthenticationProvider.class,
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
I want to handle this exception and return my custom response to the client.
I don't want to handle this by writing custom ProviderManager.
For all other OAuth exceptions i am able to handle the exceptions using Custom WebResponseExceptionTranslator.
But I am unable to catch security exceptions like InternalAuthenticationServiceException.class.
I don't have option to use ErrorController with the /error path, it is breaking other flows.
You can write a class which is annotated with #ControllerAdvice and have a #ExceptionHandler(value=InternalAuthenticationServiceException.class).
Ex:-
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(InternalAuthenticationServiceException.class)
public ResponseEntity<String> handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
ResponseEntity<String> response = new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
return response;
}
}
UPDATE
If you don't have controllers and using #EnableAuthorizationServer then you need to extend from AuthorizationServerConfigurerAdapter and override configure(AuthorizationServerEndpointsConfigurer endpoints) as below. You can use AuthorizationServerEndpointsConfigurer.exceptionTranslator to handle your InternalAuthenticationServiceException.
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// other endpoints
.exceptionTranslator(e -> {
if (e instanceof InternalAuthenticationServiceException) {
InternalAuthenticationServiceException internalAuthenticationServiceException = (InternalAuthenticationServiceException) e;
// return a ResponseEntity or throw a custom Exception.
}
});
}
First you need to implements your own AuthenticationEntryPoint the name is not really autoexplicative...
For example if you need to return always status code 200 (only for learning purpose, please don´t do it in real world...)
#Component("myOwnAuthenticationEntryPoint")
public class MyOwnAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_OK, "Unauthorized");
}
Then in your WebSecurityConfig you need to set it as your authentication exception handler entry point.
...
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Autowired
MyOwnAuthenticationEntryPoint myOwnAuthenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(myOwnAuthenticationEntryPoint);
...
}
Thats all. :)
I've solved that problem by override unsuccessfulAuthentication method in my filter and send an error response to the client with the desired HTTP status code. In my case, I also created my custom exception (RecordNotFoundException) that is thrown from my service.
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException failed) throws IOException, ServletException {
if (failed.getCause() instanceof RecordNotFoundException) {
response.sendError((HttpServletResponse.SC_NOT_FOUND), failed.getMessage());
}
}

Categories

Resources