AOP @Around: return BAD_REQUEST response

8k Views Asked by At

In a Spring rest application, every single URL must start with an application id (appId). This appId must be validated in every single rest service. Instead of duplicating code, I tried to create an @Aspect with an @Around advice. This is correctly executed before any rest method.

However, if the application id is unknown, I do not want neither to create a stack trace or neither to return a 200 (response OK). Instead I do want to return a BAD_REQUEST response code.

If I throw an exception in my advice, I get a stack trace and no HTTP response. If I on the other hand return anything else (but do not call the pjp.proceed), I get a return code of 200.

Could anyone please assist me on returning a response code 400 to the requestor?

Below my code so far:

@Component
@Aspect
public class RequestMappingInterceptor {

    @Autowired
    ListOfValuesLookupUtil listOfValuesLookupUtil;

    @Around("@annotation(requestMapping)")
    public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {
        Object[] arguments = pjp.getArgs();
        if(arguments.length == 0 || !listOfValuesLookupUtil.isValidApplication(arguments[0].toString())) {
            // toto : return bad request here ...
            throw new BadRequestException("Application id unknown!");
        } else {
            return pjp.proceed();
        }
    }
}
5

There are 5 best solutions below

0
On

There are multiple ways to handle it.

  1. One way is to use required = true at controller parameter level.

    .. @RequestHeader(value = "something", required = true) final String something ..

Reference : @RequestHeader required property behavior for request paramter and value

Additionally, you could you ExceptionControllerAdvice which handles the UnrecognizedPropertyException; Optionally you could create Error object to have better response method.

Example

@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(value = UnrecognizedPropertyException.class)
    public ResponseEntity<Error> handle(@Nonnull final UnrecognizedPropertyException exception) {
        final Error error = new Error();
        error.setMessage(exception.getOriginalMessage());
        error.setField(exception.getPropertyName());
        error.setType(HttpStatus.BAD_REQUEST.name());
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);

    }
}

Or, if you just want to return string

@ExceptionHandler(value = UnrecognizedPropertyException.class)
public ResponseEntity<String> handle(@Nonnull final UnrecognizedPropertyException exception) {
    // If you don't want to use default message just use "Application Id Unknown!" instead of exception.getOriginalMessage()
    return new ResponseEntity<>(exception.getOriginalMessage(), HttpStatus.BAD_REQUEST);
}
  1. It can be done with help of aspect as well

     @Component
     @Aspect
     public class RequestMappingInterceptor {
     @Autowired
     ListOfValuesLookupUtil listOfValuesLookupUtil;
    
     @Around("@annotation(requestMapping)")
     public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {
         Object[] arguments = pjp.getArgs();
         if(arguments.length == 0 || !listOfValuesLookupUtil.isValidApplication(arguments[0].toString())) {
             // toto : return bad request here ...
             throw new BadRequestException("Application id unknown!");
         } else {
             return pjp.proceed();
         }
     }
    }
    

    Here you would require to handle BadRequestExcption either in controller or ExceptionControllerAdvice

     @RestControllerAdvice
         public class ExceptionControllerAdvice {
    
             @ExceptionHandler(value = BadRequestExcption.class)
             public ResponseEntity<Error> handle(@Nonnull final BadRequestExcption exception) {
                 return new ResponseEntity<>(exception.getMessage(), HttpStatus.BAD_REQUEST);
             }
         }
    
0
On

I had a similar problem with a rest api exposing a Single. What worked for me in the end was this:

@Component
@Aspect
public class RequestMappingInterceptor {

  @Around("@annotation(validRequest)")
  public Object around(ProceedingJoinPoint pjp, ValidRequest validRequest) throws Throwable {
    Object[] arguments = pjp.getArgs();
    Boolean flag = validationMethod(arguments, validRequest);
    return flag ? pjp.proceed() : Single.error(new BadRequestException("Value is invalid!"))
  }
} 
0
On

You need to access the HttpServletResponse and use that to send the error code. You can do this via the RequestContextHolder

@Around("@annotation(requestMapping)")
public Object around(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {
    Object[] arguments = pjp.getArgs();
    if(arguments.length == 0 || !listOfValuesLookupUtil.isValidApplication(arguments[0].toString())) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse());
        response.sendError(HttpStatus.PRECONDITION_FAILED.value(), "Application Id Unknown!");
        return null;
    } else {
        return pjp.proceed();
    }
}
1
On

you can try returning a response entity

  if(arguments.length == 0 || !listOfValuesLookupUtil.isValidApplication(arguments[0].toString())) {
      return new ResponseEntity<>("Application id unknown!", HttpStatus.BAD_REQUEST);
  }else
0
On

You could try using a OncePerRequestFilter rather than an Aspect.

Create a filter class

public class URLFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
  FilterChain filterChain) throws ServletException, IOException {
    if(!request.getRequestURL().toString().contains("appId") { 
      response.setStatus(401);
      return;
    }
    filterChain.doFilter(request, response);
  }

Create a Spring bean for your filter in your configuration (or use @Component)

<bean id="urlFilter" class="com.xyz.filter.URLFilter" />

Then add the filter to your web.xml

<filter>
    <filter-name>urlFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
    <filter-name>urlFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Caveat: Not tested, and you could probably implement the filter in a cleaner way