Spring validation ConstraintViolationException cannot be caught using ControllerAdvice - spring-mvc

I am building APIs with spring rest and trying to validate the input parameters, like this:
#RequestMapping("/myUrl")
#RestController
#Validated
public class MyController {
#ResponseStatus(HttpStatus.OK)
#RequestMapping(value = "getSomething", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public myResponse getMarketResult(
#RequestParam #NotNull #NotBlank #NotEmpty String inputParam) {
//...my implementation
}
}
The validation does work in all these scenarios (null, blanks, empty).
MissingServletRequestParameterException is thrown for null, ConstraintViolationException is thrown for blanks/empty.
Controller advice:
#ControllerAdvice
public class ControllerValidationHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(NestedServletException.class)
public ResponseEntity<Object> processValidationError(NestedServletException ex, HttpStatus status, WebRequest request) {
// my implementation
}
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> processValidationError(ConstraintViolationException ex, HttpStatus status, WebRequest request) {
// my implementation
}
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// my implementation
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleExpception(HttpServletRequest req, Exception ex, WebRequest request) {
// my implementation
}
However I observe:
ConstraintViolationException is thrown as the cause of org.springframework.web.util.NestedServletException.
ControllerAdvice is unable to catch ConstraintViolationException and NestedServletException (But MissingServletRequestParameterException works), it's now returning a html (instead of json).
The 2 exceptions are not processed by 'handleException' of ResponseEntityExceptionHandler as well.
How do I solve 1 and 2? I'm using Spring version 4.3.1
Edit 1:
One more example, if I have a parameter like this:
#RequestParam #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) #NotNull LocalDate localDate
Expcetion is thrown as mentioned in (1) when the request is empty (eg '/myURL/getSomething?localDate='

Related

Springboot app ResponseBodyAdvice Not triggered

I am trying to update an audit entry using the response body advice but as far as I can tell it never gets executed. I see the bean in the logs:
{"timestamp":"2018-08-21T15:48:08.349Z","level":"INFO","thread":"main",
"logger":"org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter",
"message":"Detected ResponseBodyAdvice bean in responseAuditAdvice","context":"default"}
My controller method looks like this:
#PostMapping(path = "/stage", consumes = {
"application/json"
}, produces = {
"application/json"
})
#ResponseBody
public ResponseEntity<?> stage(#Valid #RequestBody StagingDto stagingDto,
#RequestHeader(HttpHeaders.USER_AGENT) String userAgent,
BindingResult bindingResult) {
I have a RequestAuditAdvice that extends RequestBodyAdviceAdapter and it is working fine. Also if the error flow occurs I see the exception advice executing as well. it is only the response advice that is failing to trigger. Any suggestions?
here is the advice bean:
#Slf4j
#RequiredArgsConstructor(onConstructor_ = #Inject)
#ControllerAdvice
public class ResponseAuditAdvice implements ResponseBodyAdvice<Object> {
private final RequestService requestService;
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
log.info("Updating audit for response.");
String ip = new String (request.getRemoteAddress().getAddress().getAddress());
requestService.auditResponse(ip, 200);
return body;
}
}

CDI Interceptor not simultaneous to current request in servlet

Sorry for the title, i have no idea to get better title.
I create a simple servlet and implements CDI interceptor.
This is my servlet
#Inject
UserManagement user;
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
user.setUsername(request.getParameter("username"));
response.getWriter().println(user.getMessage());
}
This is UserManagement bean:
#SessionScoped
public class UserManagement implements Serializable{
private String username;
private String message = "";
#UsernameSet
public void setUsername(String username){
this.username = username;
}
}
This is interceptor binding (#UsernameSet):
#InterceptorBinding
#Inherited
#Target({ TYPE, METHOD })
#Retention(RUNTIME)
#Documented
public #interface UsernameSet {
}
This is the interceptor:
#UsernameSet
#Interceptor
public class UserInterceptor implements Serializable {
#Inject
UserManagement user;
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
LOGGER.info("Entering method: " + ic.getMethod().getName());
user.setMessage("Hello " + user.getUsername());
return ic.proceed();
}
}
The interceptor does work but the servlet return UserManagement bean from previous request, although the logger shows immediately.
example
First Request:
localhost?username=smitty
Browser screen:
Hello
Second Request:
localhost?username=werben
Browser screen:
Hello smitty
Third Request:
localhost?username=jensen
Browser screen:
Hello werben
When you create an interceptor, it's invoked either before or after your method is called. With the way you have written yours, it is invoked before your method is called. InvocationContext.proceed will effectively call your method (and other interceptors if others are wired in).
To get the behavior you're looking for, you can switch to this:
#UsernameSet
#Interceptor
public class UserInterceptor implements Serializable {
#Inject
UserManagement user;
#AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
LOGGER.info("Entering method: " + ic.getMethod().getName());
Object result = ic.proceed();
user.setMessage("Hello " + user.getUsername());
return result;
}
}

Inconsistent auto-decoding with spring #RequestParam

I have a normal spring #Controller which takes an URL-encoded string as parameter:
#RequestMapping(value = "/wechat/browser", method = GET)
public void askWeChatWhoTheUserIs(#RequestParam(name = "origin") String origin,
HttpServletResponse response) throws IOException {
//omitted codes
}
When I debug the spring boot application and test the endpoint with browser:
curl http://localhost:8080/wechat/browser\?origin\=http%3A%2F%2Fwww.example.com%2Findex.html%3Fa%3Db%23%2Froute
The origin got decoded automatically and equal to http://www.example.com/index.html?a=b#/route
But when I wrote a spring mvc test:
#RunWith(SpringRunner.class)
#WebMvcTest(WeChatOauthController.class)
public class WeChatOauthControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void itShouldRedirectToWeChatToFinishOauthProtocol() throws Exception {
String origin = "http://www.example.com/index.html?a=b#/route";
String encodedOrigin = URLEncoder.encode(origin, "UTF-8");
this.mvc.perform(get("/wechat/browser")
.param("origin", encodedOrigin))
.andDo(print())
//omitted codes
}
}
When I debug this test and the controller, the origin was not decoded this time. Just wondering why it behaves differently in these two cases.
When supplying a request parameter with the Spring MVC Test framework, there is no need to manually encode the parameter's value since there is no physical HTTP request.
So, just use the original raw value in your test, and it should work fine.
In other words, use this:
#RunWith(SpringRunner.class)
#WebMvcTest(WeChatOauthController.class)
public class WeChatOauthControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void itShouldRedirectToWeChatToFinishOauthProtocol() throws Exception {
this.mvc.perform(get("/wechat/browser")
.param("origin", "http://www.example.com/index.html?a=b#/route"))
.andDo(print())
//omitted codes
}
}
You can use this method , thus there will be proper decoding
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class WeChatOauthControllerTest {
#LocalServerPort
private int port;
TestRestTemplate restTemplate = new TestRestTemplate();
#Test
public void testAmpersandEncoded(){
ResponseEntity<String> response =
restTemplate.exchange(createURI("%26"),HttpMethod.GET, null, String.class);
assertEquals(response.getStatusCode(), HttpStatus.OK);
}
private URI createURI(String param){
URI uri = null;
String url = "http://localhost:"+ port +"/location?query=" + param;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
log.error(e.getMessage());
}
return uri;
}

Class level request mapping #requestmapping execute some code on every request to that class

#Controller
#RequestMapping(value = {"user"})
public class UserController {
...
#RequestMapping(value = {"dashboard"})
public String index(HttpServletRequest req, ModelMap map) {
this.objSession = req.getSession(false);
try {
System.out.println(this.objSession.getAttribute("userid"));
I am using Spring 4.2.
Suppose I have this class and I want to check the session object having the attribute userid=1 or not.
I am doing this checking in every methods under this "/user" request.
My query is that if I can avoid this same coding which i am doing before executing any codes of any methods.
Is there any way round to increase code resuability for checking ?
In advance thanks for your time.
You can make use of interceptors by matching the request path.
<mvc:interceptors path-matcher="/someRequest/*">
<bean class="className" autowire="constructor"/>
</mvc:interceptors>
In path-mathcer you can specify one type of url,so that it will execute whatever you require.
Controller:
#Controller
#RequestMapping(value = {"someRequest/user"})
public class UserController {
Interceptor: Before processing someRequest/user you can use a interceptor like below, in 3 ways you can use->afterCompletion ,preHandle,postHandle.
In you case code would be written in preHandle method
public class SomeRequestIntercept implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object,
Exception exception) throws Exception {
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object arg2, ModelAndView arg3)
throws Exception {
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object arg2) throws Exception {
System.out.println("before processing someRequest/**");
return true;
}
}

advise controller method *before* #Valid annotation is handled

I am adding rate-limiting to a restful webservice using Spring MVC 4.1.
I created a #RateLimited annotation that I can apply to controller methods. A Spring AOP aspect intercepts calls to these methods and throws an exception if there have been too many requests:
#Aspect
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class RateLimitingAspect {
#Autowired
private RateLimitService rateLimitService;
#Before("execution(* com.example..*.*(.., javax.servlet.ServletRequest+, ..)) " +
"&& #annotation(com.example.RateLimited)")
public void wait(JoinPoint jp) throws Throwable {
ServletRequest request =
Arrays
.stream(jp.getArgs())
.filter(Objects::nonNull)
.filter(arg -> ServletRequest.class.isAssignableFrom(arg.getClass()))
.map(ServletRequest.class::cast)
.findFirst()
.get();
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedAttempt(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
}
}
This all works perfectly, except when the #RateLimited controller method has parameters marked as #Valid, e.g.:
#RateLimited
#RequestMapping(method = RequestMethod.POST)
public HttpEntity<?> createAccount(
HttpServletRequest request,
#Valid #RequestBody CreateAccountRequestDto dto) {
...
}
The problem: if validation fails, the validator throws MethodArgumentNotValidException, which is handled by an #ExceptionHandler, which returns an error response to the client, never triggering my #Before and therefore bypassing the rate-limiting.
How can I intercept a web request like this in a way that takes precedence over parameter validation?
I've thought of using Spring Interceptors or plain servlet Filters, but they are mapped by simple url-patterns and I need to differentiate by GET/POST/PUT/etc.
I eventually gave up on trying to find an AOP solution and created a Spring Interceptor instead. The interceptor preHandles all requests and watches for requests whose handler is #RateLimited.
#Component
public class RateLimitingInterceptor extends HandlerInterceptorAdapter {
#Autowired
private final RateLimitService rateLimitService;
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
rateLimit(request, (HandlerMethod)handler);
}
return super.preHandle(request, response, handler);
}
private void rateLimit(HttpServletRequest request, HandlerMethod handlerMethod) throws TooManyRequestsException {
if (handlerMethod.getMethodAnnotation(RateLimited.class) != null) {
String ip = request.getRemoteAddr();
int secondsToWait = rateLimitService.secondsUntilNextAllowedInvocation(ip);
if (secondsToWait > 0) {
throw new TooManyRequestsException(secondsToWait);
} else {
rateLimitService.recordInvocation(ip);
}
}
}
}
Add the following controller advice in your application.
#ControllerAdvice
public class ApplicationControllerAdvice {
#InitBinder
#RateLimited
protected void activateBeanPropertyAccess(DataBinder dataBinder) {
dataBinder.initBeanPropertyAccess();
}
}
The #RateLimited should call the class RateLimitingAspect. So, after this all the constraints validator will be called.
See if it's feasible for you to implement similar logic for ##AfterThrowing advice as well which will have similar pointcut.

Resources