7

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();
        }
    }
}
zip4ever
  • 121
  • 1
  • 7
  • I am an AspectJ expert, but not a Spring MVC one. Please provide me with some more coordinates and show me some method signatures (return types and parameters) you want to intercept. Do your request mapping methods actually return something like `ResponseEntity`? I need the full picture in order to answer. – kriegaex Jun 27 '15 at 15:55
  • Please check my answer in this thread. I explain and provide a sample code https://stackoverflow.com/a/50712697/3073945 – Md. Sajedul Karim Jun 06 '18 at 05:38

5 Answers5

3

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();
    }
}
bstick12
  • 1,699
  • 12
  • 18
1

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
Sarfaraz Khan
  • 2,166
  • 2
  • 14
  • 29
  • Hmz, still get a 200 return code, which is strange cause I can see this return clearly happen in debug mode : java.lang.AssertionError: Response status Expected :400 Actual :200 – zip4ever Jun 29 '15 at 07:36
1

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);
             }
         }
    
Danyal Sandeelo
  • 12,196
  • 10
  • 47
  • 78
Pratiyush Kumar Singh
  • 1,977
  • 3
  • 19
  • 39
0

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!"))
  }
} 
-1

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

Adam
  • 2,214
  • 1
  • 15
  • 26