springspring-bootaccess-deniedspring-boot-security

Spring boot security catch access denied with GlobalExceptionHandler


I want to catch the spring boot access denied exception when for example a user is about to access a page that is for an admin.

I have this exception handler in my GlobalExceptionHandler (@ControllerAdvice)

    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public String handleAccessDenied(AccessDeniedException e, Model model) {
        model.addAttribute("errorMessage", e.getMessage());
        return "error";
    }

I tried using a CustomAccessDeniedHandler:

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        throw accessDeniedException;
    }
}

and in my SecurityConfig i added this to my filterChain:

.exceptionHandling(eh -> eh.accessDeniedHandler(customAccessDeniedHandler))

And now i get an Access Denied in my terminal: org.springframework.security.authorization.AuthorizationDeniedException: Access Denied

I want to catch this with my GlobalExceptionHandler (tried using AuthorizationDeniedException in ExceptionHandler)

Note: I dont use JWT

What am i missing?


Solution

  • Spring Security throws AccessDeniedException in FilterSecurityInterceptor, before the request reaches any controller, so @ControllerAdvice cannot catch it.

    Instead, you can try to handle it inside AccessDeniedHandler. In your code, exception is throwing again, so it's not caught anywhere.

    Try to change handler to do something like this:

    @Component
    public class CustomAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(
                HttpServletRequest request,
                HttpServletResponse response,
                AccessDeniedException accessDeniedException
        ) throws IOException {
            request.setAttribute("errorMessage", accessDeniedException.getMessage());
            response.sendRedirect("/access-denied");
        }
    }
    

    Then add a controller to handle /access-denied:

    @Controller
    public class ErrorController {
    
        @GetMapping("/access-denied")
        public String accessDenied(Model model, HttpServletRequest request) {
            String msg = (String) request.getAttribute("errorMessage");
            model.addAttribute("errorMessage", msg);
            return "error";
        }
    }
    

    This way you can show a custom error page with your message.