How to catch spring boot AccessDeniedException when using #PreAuthorize - spring-boot

I am trying to catch AccessDeniedException, so I can display the AccessDenied error message in response, instead of Internal Server Error(s) while executing query
But, when I try to debug the method, try/catch block doesn't catch the AccessDeniedException thrown by filterChain.doFilter(request, response);
I tried to use recommended #ExceptionHandler , but they did not help either.
Current response:
{
"errors": [
{
"message": "Internal Server Error(s) while executing query"
}
]
}
Expected response:
{
"errors": [
{
"message": "Access denied."
}
]
}
Here is my code:
#Component
public class JwtFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(#NotNull HttpServletRequest request, #NotNull HttpServletResponse response, #NotNull FilterChain filterChain) {
Optional<HttpServletRequest> optReq = Optional.of(request);
String authToken = optReq
.map(req -> req.getHeader("Authorization"))
.filter(token -> !token.isEmpty())
.map(token -> token.replace("Bearer ", ""))
.orElse(null);
if (authToken != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//process token
} else {
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | ServletException | IOException e) {
String test = "test";
}
}
}
}
Here is the mutation where #Preauthorized is used :
#Component
#PreAuthorize("hasAnyRole('CREATOR','INFLUENCER','INFLUENCER_TEAMMATE')")
public class ProducerSharedMutations implements GraphQLMutationResolver {
private final WidgetService widgetService;
public ProducerSharedMutations(WidgetService widgetService) {
this.widgetService = widgetService;
}
public Widget addWidget(WidgetInput widgetInput){
return widgetService.add(widgetInput);
}
}

Related

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Interceptor and global exception handling

I have a post-interceptor. When the control layer is executed and returns information, the post-interceptor will be executed. At this time, an exception in the post-interceptor will be caught by the global exception handling and a prompt message will be returned. Use "postman" to test and control The information of layer and global exception handling is returned at the same time. Is this really returned? I wrote a test example. In the same situation, only the information of the control layer is returned. I think it should return the information of global exception handling.
Controller
#RestController
#RequestMapping("/v1/book")
#Validated
public class BookController {
private final BookService bookService;
public BookController(BookService bookService) {
this.bookService = bookService;
}
#GetMapping("/search")
public R searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return R.select(bookService.getBookByKeyword(q));
}
}
Interceptor
public class LogInterceptor extends HandlerInterceptorAdapter {
public LogInterceptor(LoggerResolver loggerResolver) {
this.loggerResolver = loggerResolver;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// There will be a runtime exception here
}
}
Global Exception Handing
#Order
#RestControllerAdvice
#Slf4j
public class RestExceptionHandler {
/**
* Exception
*/
#ExceptionHandler({Exception.class})
public R processException(Exception exception) {
log.error("", exception);
return R.error();
}
}
Result
{
"code": 200,
"data": [
// ...
],
"type": "success",
"message": "OK"
}{
"code": 500,
"type": "error",
"message": "Internal Server Error"
}
"R extends HashMap<String, Object>", used to unify the return structure.
looking at your code snippet, I'm not sure what are those R in the searchBook and processException
try this (edit the processException to meet your specs):
#GetMapping("/search")
public ResponseEntity<?> searchBook(#RequestParam(value = "q", required = false, defaultValue = "") String q) {
return new ResponseEntity<>(bookService.getBookByKeyword(q), HttpStatus.OK);
}
#ExceptionHandler({Exception.class})
public ResponseEntity<?> processException(Exception exception) {
return new ResponseEntity<>(new ErrorDTO(exception.getMessage()), HttpStatus.UNPROCESSABLE_ENTITY);
}

How to set implementation of OncePerRequestFilter to filter only one endpoint

Im using this Filter to log my requests and responses, it works very well, but I noticed that I didnt need actualy this filter for all my endpoints - it will be more efficient and enough to filtering and logging requests from only one endpoint.
Is it possible without making if statement in afterRequest method?
Im searching this a lot, but almost every example is with spring security :(
#Slf4j
#Component
public class RequestAndResponseLoggingFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
}
protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
beforeRequest(request, response);
try {
filterChain.doFilter(request, response);
}
finally {
afterRequest(request, response);
response.copyBodyToResponse();
}
}
protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val queryString = request.getQueryString();
if (queryString == null) {
log.info("{}> {} {}", address, request.getMethod(), request.getRequestURI());
} else {
log.info("{}> {} {}?{}", address, request.getMethod(), request.getRequestURI(), queryString);
}
Collections.list(request.getHeaderNames()).forEach(headerName ->
Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
log.info("{}> {}: {}", address, headerName, headerValue)));
val content = request.getContentAsByteArray();
if (content.length > 0) {
log.info("{}>", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}> {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}> [{} bytes body]", address, content.length);
}
}
log.info("{}>", address);
}
}
protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) {
if (log.isInfoEnabled()) {
val address = request.getRemoteAddr();
val status = response.getStatus();
log.info("{}< {} {}", address, status, HttpStatus.valueOf(status).getReasonPhrase());
response.getHeaderNames().forEach(headerName ->
response.getHeaders(headerName).forEach(headerValue ->
log.info("{}< {}: {}", address, headerName, headerValue)));
val content = response.getContentAsByteArray();
if (content.length > 0) {
log.info("{}<", address);
try {
val contentString = new String(content, request.getCharacterEncoding());
Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> log.info("{}< {}", address, line));
} catch (UnsupportedEncodingException e) {
log.info("{}< [{} bytes body]", address, content.length);
}
}
}
}
private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
if (request instanceof ContentCachingRequestWrapper) {
return (ContentCachingRequestWrapper) request;
} else {
return new ContentCachingRequestWrapper(request);
}
}
private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
if (response instanceof ContentCachingResponseWrapper) {
return (ContentCachingResponseWrapper) response;
} else {
return new ContentCachingResponseWrapper(response);
}
}
}

How to validated rest url in spring boot?

validate Rest URL in spring boot.
Requirement: If I hit the wrong URL then it should throw a custom exception.
ex. Correct URL is "/fulfillment/600747l/send_to_hub" If I hit "/api/600747l/send_to_hub_1" then it should return exception like
"404:- URL not Found.".
Right now it returning "500 : -
{
"timestamp": 1531995246549,
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Request URL.",
"path": "/api/600747l/send_to_hub_1"
}"
you need to write NewClass with annotation #ControllerAdvice which will redirect all exceptions to this NewClass.
example
Your Custom Exception Class:
#Data
#AllArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class IOApiException extends IOException {
private ErrorReason errorReason;
public IOApiException(String message, ErrorReason errorReason) {
super(message);
this.errorReason = errorReason;
}
}
Now the CustomExceptionHandler Class -
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = IOApiException.class)
public GlobalErrorResponse handleException(IOApiException e) {
logger.error("UNAUTHORIZED: ", e);
return new GlobalErrorResponse("URL Not Found", HttpStatus.UNAUTHORIZED.value(), e.getErrorReason());
}
//this to handle customErrorResponseClasses
public GlobalErrorResponse getErrorResponseFromGenericException(Exception ex) {
if (ex == null) {
return handleException(new Exception("INTERNAL_SERVER_ERROR"));
}
else if (ex instanceof IOApiException) {
return handleException((IOApiException) ex);
}
}
Now Your error response class:
public class GlobalErrorResponse {
private String message;
#JsonIgnore
private int statusCode;
private ErrorReason reason;
}
ErrorReason Class
public enum ErrorReason {
INTERNAL_SERVER_ERROR,
INVALID_REQUEST_PARAMETER,
INVALID_URL
}
add and register one filter who calls the GlobalExceptionHandler in exception case like this
public class ExceptionHandlerFilter implements Filter {
private final GlobalExceptionHandler globalExceptionHandler;
public ExceptionHandlerFilter(GlobalExceptionHandler globalExceptionHandler) {
this.globalExceptionHandler = globalExceptionHandler;
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception exception) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
GlobalErrorResponse errorResponse = globalExceptionHandler.getErrorResponseFromGenericException(exception);
httpResponse.setStatus(errorResponse.getStatusCode());
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
#Override
public void destroy() {
}
}
Like this you can add as many exceptions you want.. and can handle it manually.
As per your question first of all you need to define a base url(e.g.-/api) so that any url must be handled through your controller.Now after base url as shown /api/600747l/send_to_hub_1 #PathVariable int id. This circumstance is important, because Spring documentation said that if method argument annotated with #PathVariable can’t be casted to specified type (in our case to int), it will be exposed as String. Hence it can cause a TypeMismatchException.
To handle this I will use #ExceptionHandler annotation on #Controller level. Such approach suits for this situation as no one else. I just need to make 2 changes in the Controller:
1.Add MessageSource field
2.Add exception handler method
#Autowired
private MessageSource messageSource;
...
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.smartphone.id", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
return new ErrorInfo(errorURL, errorMessage);
}
...

Grails 2.5.2 Spring Security Custom Filter - Error Cannot set request attribute - request is not active anymore

I have created a custom spring security filter to perform HMAC token based auth for the api calls our client make.
Here's what the filter looks like:
class BqCustomTokenFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
def authenticationManager
def customProvider
AuthenticationSuccessHandler authenticationSuccessHandler
AuthenticationFailureHandler authenticationFailureHandler
SessionAuthenticationStrategy sessionAuthenticationStrategy
ApplicationEventPublisher applicationEventPublisher
#Override
void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse response = (HttpServletResponse) resp
/* wrap the request in order to read the inputstream multiple times */
MultiReadHttpServletRequest request = new MultiReadHttpServletRequest((HttpServletRequest) req);
if (!request.getRequestURI().startsWith('/api/')) {
// Should not happen
chain.doFilter(request, response)
return
}
logger.trace("filter called from remote IP = ${req.remoteAddr} for URL ${request.getRequestURI()}")
def requestBody = request.inputStream.getText()
final AuthHeader authHeader = HmacUtil.getAuthHeader(request);
if (authHeader == null) {
// invalid authorization token
logger.warn("Authorization header is missing");
authenticationFailureHandler.onAuthenticationFailure(request.getRequest(), response, null)
//unsuccessfulAuthentication(request, response, null)
return
}
final String apiKey = authHeader.getApiKey();
logger.trace("got request for apiKey = ${apiKey}")
if (apiKey) {
def myAuth = new BqAuthenticationToken(
credentials: apiKey,
authHeader: authHeader,
payload: requestBody,
requestDetails: [
scheme : request.getScheme(),
host : request.getServerName() + ":" + request.getServerPort(),
method : request.getMethod(),
resource : request.getRequestURI(),
contentType: request.contentType,
date1 : request.getHeader(HttpHeaders.DATE)
],
authenticated: false
)
try {
myAuth = authenticationManager.authenticate(myAuth)
if (!myAuth.authenticated) {
logger.warn("Authorization header does not match", ex)
//unsuccessfulAuthentication(request, response, ex)
//return
logger.warn("Could not authenticate")
SecurityContextHolder.clearContext()
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
return
}
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated!!")
}
sessionAuthenticationStrategy.onAuthentication(myAuth, request, response)
} catch (BadCredentialsException | Exception ex) {
logger.warn("Authorization header does not match", ex)
//unsuccessfulAuthentication(request, response, ex)
//return
logger.warn("Could not authenticate")
SecurityContextHolder.clearContext()
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
//respo.setStatus(statuscode)
//authenticationFailureHandler.onAuthenticationFailure((HttpServletRequest) request, response, ex)
}
try {
chain.doFilter(request, response)
return
} catch (IllegalStateException ex) {
logger.warn("=====> IllegalStateException", ex)
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED)
}
} else {
logger.warn("No API Key found in Request")
SecurityContextHolder.clearContext()
authenticationFailureHandler.onAuthenticationFailure((HttpServletRequest) request, response, null)
}
}
void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher
}
}
resources.groovy
sessionAuthenticationStrategy(NullAuthenticatedSessionStrategy)
bqCustomTokenFilter(com.bq.security.client.BqCustomTokenFilter) {
authenticationManager = ref("authenticationManager")
customProvider = ref("bqTokenAuthenticationProvider")
authenticationSuccessHandler = ref('authenticationSuccessHandler')
authenticationFailureHandler = ref('authenticationFailureHandler')
sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
}
config.groovy
grails.plugin.springsecurity.providerNames = [
'bqTokenAuthenticationProvider',
'daoAuthenticationProvider',
'anonymousAuthenticationProvider',
'rememberMeAuthenticationProvider']
grails.plugin.springsecurity.filterChain.filterNames = [
'securityContextPersistenceFilter',
'logoutFilter',
'authenticationProcessingFilter',
'bqCustomTokenFilter',
'concurrencyFilter',
'switchUserProcessingFilter',
'rememberMeAuthenticationFilter',
'anonymousAuthenticationFilter',
'exceptionTranslationFilter',
'filterInvocationInterceptor',
]
grails.plugin.springsecurity.filterChain.chainMap = [
'/api/**': 'bqCustomTokenFilter',
'/**' : 'JOINED_FILTERS,-bqCustomTokenFilter'
]
I also have multitenant-single-db plugin installed. Authentication works absolutely fine. However, I get the following errors sporadically:
2016-10-19 14:06:51,120 +0530 [http-nio-8080-exec-1] ERROR UrlMappingsFilter:213 - Error when matching URL mapping [/api/execution/updateResult]:Cannot set request attribute - request is not active anymore!
java.lang.IllegalStateException: Cannot set request attribute - request is not active anymore!
at grails.plugin.multitenant.core.servlet.CurrentTenantServletFilter.doFilter(CurrentTenantServletFilter.java:53)
at com.bq.security.client.BqCustomTokenFilter$$EQ0438yG.doFilter(BqCustomTokenFilter.groovy:142)
at grails.plugin.springsecurity.web.filter.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:102)
at grails.plugin.springsecurity.web.filter.DebugFilter.doFilter(DebugFilter.java:69)
at com.brandseye.cors.CorsFilter.doFilter(CorsFilter.java:100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
This normally happens when clients are sending post requests frequently. Since I have retry logic built into the client, it works fine, however this exception is very annoying.
Any help is much appreciated.

Resources