0

I am using Spring Boot framework for my backend application and Keycloak for my user and access management system. I have a problem regarding creating a costume exception handler for 403 forbidden error.

I already read this link and this link. These questions are about creating custom message when 403 error is raised. Both of the answers did not help me since I have a general exception handler.

Without any general exception handler, I get proper 401 and 403 responses regarding unauthorized tokens. But I want to have a general exception handler for unexpected errors. Following is my general exception handler:

@ExceptionHandler(value = Exception.class)
public ResponseEntity<String> generalExceptionHandler(Exception e) {

    log.error(e.getMessage());

    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("General Error");
}

Without any other exception handler all situations that result in 401 or 403 response are handled by my generalExceptionHandler, which is not preferred. Since I want to send the proper message to the frontend in case of occurrence of 401 or 403 errors.

Therefore I developed an exception handler for Access denied exception like following:

@ExceptionHandler(value = AccessDeniedException.class)
public ResponseEntity<String> accessDeniedExceptionHandler(Exception e) {

    log.error(e.getMessage());

    return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Access is denied"));
}

In this step all situations regarding the 401 situation are handled successfully with accessDeniedExceptionHandler.

The problem is that, situations regarding 403 Forbidden now are handled by accessDeniedExceptionHandler too. Since I want to send the proper message to frontend in case of 403 situation, I want to have a separate handler for this case. The reason relies in importance of distinguishing 401 and 403 errors in my software.

Can somebody please help me to fix this problem?

Reza Azad
  • 45
  • 7

2 Answers2

1

My Solution

According to this question, and this answer I developed the following classes and beans.

This is the custom authentication entry point class for producing desired response for 401 exceptions:

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint 

@Override
public void commence(HttpServletRequest req, HttpServletResponse res, AuthenticationException authException) throws IOException, ServletException {
    res.setContentType("application/json;charset=UTF-8");
    res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

    // Create response content
    JSONObject obj=new JSONObject();
    obj.put("code", HttpServletResponse.SC_UNAUTHORIZED);
    obj.put("message", "Access Denied");


    res.getWriter().write(obj.toString());

    }
}

This is the custom access denied handler class for producing desired response for 403 exceptions:

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


    // Set response code
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);

    // Set response content type to JSON
    response.setContentType("application/json;charset=UTF-8");

    // Create response content
    JSONObject obj=new JSONObject();
    obj.put("code", HttpServletResponse.SC_FORBIDDEN);
    obj.put("message", "Access Forbidden");

    // Add content to the response
    response.getWriter().write(obj.toString());

    }
}

As it is explained in the above mentioned answer (Thanks to Amit Samuel), I added the following to my http security config:

http.exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint())
            .accessDeniedHandler(accessDeniedHandler());

Also, I added the following beans to web security class:

@Bean
public AuthenticationEntryPoint authenticationEntryPoint(){
    return new CustomAuthenticationEntryPoint();
}
@Bean
public AccessDeniedHandler accessDeniedHandler(){
    return new CustomAccessDeniedHandler();
}

In this step without a general exception handler I produce custom responses for 401 and 403 exceptions. But by enabling general exception handler 401 and 403 exceptions are caught by general exception handler.

Therefore I added the following exception handler alongside with the general exception handler.

@ExceptionHandler(value = Exception.class)
public ResponseEntity<String> generalExceptionHandler(Exception e) {

    log.error(e.getMessage());

    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("General Error");
}

@ExceptionHandler(value = AccessDeniedException.class)
public void accessDeniedExceptionHandler(Exception e) {
    throw new AccessDeniedException(e.getMessage());
}

By this means, whenever 401 and 403 exceptions are raised, the accessDeniedExceptionHandler will be activated and would throw a new access denied exception. Then, according to the situation, 401 or 403, proper response and code will be returned.

For now, this solution works fine for my project, but I would search for the best practice. I would be so grateful if any one has other solution.

Reza Azad
  • 45
  • 7
0

You can use this class to handle access-denied requests:

@ControllerAdvice
public class AccessDeniedExceptionHandler implements AccessDeniedHandler {

@Override
public void handle(
        HttpServletRequest httpServletRequest,
        HttpServletResponse res,
        AccessDeniedException e) throws IOException {
    APIErrorResponse apiErrorResponse = new APIErrorResponse(Collections.singletonList("Access Denied!"));
    res.setHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE);
    String message = new Gson().toJson(apiErrorResponse);
    res.sendError(HttpServletResponse.SC_FORBIDDEN, message);
   }
}