I am trying to return a 403 from my REST API, but Spring Boot is transforming the value into a 500
I’ve tried the standard solutions like writing an @ExceptionHandler or a @ControllerAdvice, but they do not seem to be working for me
My breakpoints inside the @ExceptionHandler inside the file as well as inside the @ControllerAdvice are never hit
Any help is appreciated
package some.package;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import lombok.Builder;
import lombok.Getter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class ClassThrowingADE {
private MyService myService;
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
@GET
@Path("some/api")
@Produces(MediaType.APPLICATION_JSON)
public SomeResult doSomething(...) {
if (!isUserIsAuthorized(...)) {
throw new AccessDeniedException(myMessage);
}
return myService.getSomeResult(...);
}
private boolean isUserIsAuthorized(...) {
if (!isAuthEnabled) {
return true;
}
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
DefaultOidcUser defaultOidcUser = (DefaultOidcUser) authentication.getPrincipal();
String userEmail = defaultOidcUser.getUserInfo().getEmail();
return myService.isUserEmailAuthorizedToView(..., userEmail);
}
// Same file defines @ExceptionHandler
@ExceptionHandler({AccessDeniedException.class, ServletException.class})
public void handleADE(HttpServletRequest request, HttpServletResponse response, ServletException e) throws IOException, ServletException {
if (!request.getRequestURI().startsWith("/some/api")) {
throw e;
}
Throwable t = e;
AccessDeniedException ade = null;
while (t != null) {
if (t instanceof AccessDeniedException) {
// I noticed that Spring is wrapping the ADE twice in a ServletException
ade = (AccessDeniedException) t;
break;
}
t = t.getCause();
}
if (ade != null) {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.getWriter().println(e.getMessage());
} else {
throw e;
}
}
}
@ControllerAdvice
public class MyControllerAdvice {
@ExceptionHandler(ServletException.class)
public @ResponseBody MyErrorResponse handleServletException(HttpServletRequest request, HttpServletResponse response, ServletException e) {
if (!request.getRequestURI().startsWith("some/api")) {
return null;
}
Throwable t = e;
AccessDeniedException ade = null;
while (t != null) {
if (t instanceof AccessDeniedException) {
ade = (AccessDeniedException) t;
break;
}
t = t.getCause();
}
if (ade != null) {
return MyErrorResponse.builder()
.httpStatus(HttpStatus.FORBIDDEN)
.requestURI(request.getRequestURI())
.errorMessage(e.getMessage())
.build();
} else {
return null;
}
}
@ExceptionHandler(AccessDeniedException.class)
public @ResponseBody MyErrorResponse handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
if (!request.getRequestURI().startsWith("/some/api")) {
return null;
}
return MyErrorResponse.builder()
.httpStatus(HttpStatus.FORBIDDEN)
.requestURI(request.getRequestURI())
.errorMessage(e.getMessage())
.build();
}
}
@Getter
@Builder
public class MyErrorResponse {
private HttpStatus httpStatus;
private String requestURI;
private String errorMessage;
}