Reputation: 121
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();
}
}
}
Upvotes: 7
Views: 8395
Reputation: 1997
There are multiple ways to handle it.
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);
}
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);
}
}
Upvotes: 2
Reputation: 1
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!"))
}
}
Upvotes: 0
Reputation: 1749
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();
}
}
Upvotes: 4
Reputation: 2269
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
Upvotes: -1
Reputation: 2186
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
Upvotes: 1