Spring boot interceptor to capture request values on bean validation - java

I have a spring boot app(v2.3.0.RELEASE) and I need to get any request being sent from my restcontroller when there is a bean validation error.
My Request is as follows:
public class PaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#NotBlank(message = "name.required")
private String name;
}
For instance, if name is null, I need an interceptor to capture values of transactionTypeCode and
paymentIdentification before exception is triggered.
I tried implementing the following interceptor to capture all not null parameters value being sent:
public class MyInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
//capture required valued set it in HttpServletRequest attribute to be used for exception handling
HandlerMethod h1 = (HandlerMethod) handler;
MethodParameter[] param = null;
System.out.println("MINIMAL: INTERCEPTOR PREHANDLE CALLED");
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR POSTHANDLE CALLED");
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
Enumeration<?> e = request.getParameterNames();
System.out.println("MINIMAL: INTERCEPTOR AFTERCOMPLETION CALLED");
}
When the name is null it enters the method preHandle but I am not able to get the parameters and its corresponding values being sent, any idea how to do it pls?
The reason why I am doing the above changes is because I need to set the value of transactionTypeCode and paymentIdentification in my interceptor above so as to use them below in my exception handler as follows:
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
private #Autowired
HttpServletRequest httpServletRequest;
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
// mapParam is size zero
Map<String, String[]> mapParam = httpServletRequest.getParameterMap();
if (!ObjectUtils.isEmpty(exception) && !ObjectUtils.isEmpty(this.request1)) {
paymentValidator.onErrorUpdatePayment(this.request1.getAttribute("transactionTypeCode"), this.request1.getAttribute("paymentIdentification "), exception.toString());
}
....
return new ResponseEntity<>(ipsResponse, new HttpHeaders(), ipsResponse.getHttpStatus());
}

Related

Return body in OncePerRequestFilter

Update:
After looking this over with a colleague, this is probably due to the fact that Spring apps run on Tomcat server, which is following standard protocols (https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html), and will not return a body for HEAD requests.
=====================
I have OncePerRequestFilter designed to reject any HEAD requests, as you can see here:
#Component
public class RestrictedResponseFilter extends OncePerRequestFilter {
#Autowired
#Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
#Override
public void doFilterInternal(HttpServletRequest httpRequest, HttpServletResponse httpServletResponse, FilterChain chain) throws IOException, ServletException {
if(httpRequest.getMethod().equals(HttpMethod.HEAD.name())){
resolver.resolveException(httpRequest,httpServletResponse,null,new RequestRejectedException("Http Method HEAD rejected"));
} else {
chain.doFilter(httpRequest,httpServletResponse);
}
}
}
resolver.resolveException sends the request through my ResponseEntityExceptionHandler class:
#CrossOrigin("*")
#Slf4j
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(MyExceptionHandler.class);
...
#ExceptionHandler(RequestRejectedException.class)
public ResponseEntity handleRequestRejectedException(final RequestRejectedException e, final WebRequest request){
logger.error(e.getMessage(), e);
return handleExceptionInternal(e,null,null,HttpStatus.FORBIDDEN,request);
}
private SpringErrorSwaggerDefinition buildResponse(final Exception e, final WebRequest request, final HttpStatus status){
final SpringErrorSwaggerDefinition cvdosResponse = SpringErrorSwaggerDefinition.builder()
.message(e.getMessage())
.timestamp(System.currentTimeMillis())
.error(status.getReasonPhrase())
.exception(e.getClass().getName())
.path(request.getContextPath())
.status(status.value())
.build();
return cvdosResponse;
}
#Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, #Nullable Object body, #Nullable HttpHeaders headers, HttpStatus status, WebRequest request) {
final SpringErrorSwaggerDefinition response = buildResponse(ex,request,status);
return new ResponseEntity<>(response, status);
}
}
Other Exceptions that are handled in this class return the SpringErrorSwaggerDefinition response body. However, that does not happen when passing through the filter. How can I resolve this?

Why ExceptionHandlerExceptionResolver makes redirect instead of simple response?

I've written a few extensions of ExceptionHandlerExceptionResolver, it intercepts all exceptions that it should, but instead of returning only error message and HTTP status code it makes really weird redirect by its own URL built upon users requested URL. For instance:
user's url -> .../myModule/api/myEntity/123 (it's an id)
resolver's redirect url -> .../myModule/api/myEntity/myEntity/123
Server doesn't have such resource and obviously it will respond with 404.
The question is: why it makes redirect and how to configure it to return only a message and status code?
My resolver:
public class BusinessLayerExceptionHandler extends ExceptionHandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.addObject("errorMessage", ex.getMessage());
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
return wrappedResponse;
}
}
I guess the usage of ModelAndView assumes redirection. At least that's a method description that I found in DispatcherServlet.
...
* #return a corresponding ModelAndView to forward to
* #throws Exception if no error ModelAndView found
*/
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
...
If so, how to make it return just error message and HTTP status code?
You can return just error message and HTTP status code by creating a custom View.
public class YourCustomView implements View {
private final String errorMessage;
public YourCustomView(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter pw = response.getWriter()) {
pw.write(errorMessage);
}
}
}
You need to put the custom View object into ModelAndView object in HandlerExceptionResolver#resolveException.
public class BusinessLayerExceptionHandler implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) {
ModelAndView wrappedResponse = new ModelAndView();
wrappedResponse.setStatus(HttpStatus.BAD_REQUEST);
wrappedResponse.setView(new YourCustomView(ex.getMessage()));
return wrappedResponse;
}
}
why it makes redirect
It seems that Spring recognize the view name as a defaultViewName and forwards to it (by calling RequestDispatcher#forward).
In DispatcherServlet#processHandlerException, a defaultViewName is set to the view name of a ModelAndView returned by resolveException when it doesn't have View object. A defaultViewName is got from DispatcherServlet#getDefaultViewName that translates a HTTP request into a view name.
Another Solution
I think you may be able to use #ControllerAdvice and #ExceptionHandler instead. It also can handle an exception thrown from a controller.
#ControllerAdvice
public class YourControllerAdvice {
#ExceptionHandler
public ResponseEntity<Map<String, String>> handleBusinessLayerException(
Exception exception) {
Map<String, String> body = Map.of("errorMessage", exception.getMessage());
return ResponseEntity.badRequest().body(body);
}
}
See Also
Spring Web MVC document about HandlerExceptionResolver
Spring Web MVC document about ControllerAdvice

Java Servlet retrieve Spring RequestMapping Url

I wrote a Request Interceptor to add some Information to Requests in Test-Environment.
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
...
}
public void postHandle(
HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView)
throws Exception {
...
}
Currently I'm retrieving the URLs like this:
String url = request.getServletPath();
For a Controller like this:
#RequestMapping(value = "/{id}",
method = RequestMethod.GET)
public ResponseEntity<?> getByID(#PathVariable long ID) {
...
}
And for a Request like /1/
url would be /1/
Is there any way to get the Request-Mapping-Value ==> /{id}
Thanks in advance
#RequestMapping and its composed annotation methods (i.e. #GetMapping , #PostMapping etc.) are handled by HandlerMethod. So cast the handler object to it and you can access the #RequestMapping information that you want:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
RequestMapping mapping = hm.getMethodAnnotation(RequestMapping.class);
if (mapping != null) {
for(String val : mapping.value()) {
//***This is the mapping value of #RequestMapping***
System.out.println(val);
}
}
}
}

Required request body is missing after making a copy using HttpServletRequestWrapper

In my project, I have a set of api calls which should filtered through certain set of common validation. In that case, I have to intercept the request before it hits the REST controller, read the request body, do the validations and pass it to the controller if the request passes the validations.
Since the HttpServletRequest cannot be deserialized more than once, I used a HttpServletRequestWrapper to make a copy of the actual request. Using the copy it makes, I do the validations.
Following is the configuration class for intercepting the requests.
public class InterceptorConfig extends WebMvcConfigurerAdapter {
#Autowired
CustomInterceptor customInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor).addPathPatterns("/signup/**");
}
}
Here is my preHandle method inside CustomInterceptor class which extends HandlerInterceptorAdaptor
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
return false;
}
// some more validations which end up returning false if they are met
}
return true;
}
This is my HttpRequestWrapper
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
public HttpRequestWrapper(HttpServletRequest request) throws IOException{
super(request);
try {
requestBody = IOUtils.toByteArray(request.getInputStream());
} catch (IOException ex) {
requestBody = new byte[0];
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener listener) {
throw new RuntimeException("Not implemented");
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
}
}
All set now. Now, when I send a request to any url with the pattern of /signup/**, all the validations are happening fine. However, once the request hits the controller method, error pops out saying the request body is not available.
Required request body is missing: public
com.mypackage.myResponseObject
com.mypackage.myController.myControllerMethod(com.mypackage.myDTO)
I am struggling to find the reason for this and also a way to overcome the issue. Is there anything I have done wrong in RequestWrapper class? or anything missing?
Help me to sort this thing out.
Thanks!
The Problem seems to be that you are using an Interceptor to read the HttpServletRequest's InputStream and just wrap it in HttpRequestWrapper but the wrapper is never returned.
I think you should use a Filter
public class CustomFilter extends OncePerRequestFilter {
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequest copiedRequest = new HttpRequestWrapper(request);
Map<String, Object> jsonMap = mapper.readValue(copiedRequest.getInputStream(), Map.class);
if(jsonMap.containsKey("userId")){
long userId = jsonMap.get("userId");
MyClass myObject= myAutowiredService.getMyObject(userId);
if(myObject == null){
response.setStatus(HttpStatus.SC_NOT_ACCEPTABLE);
//return false;
}
// some more validations which end up returning false if they are met
}
filterChain.doFilter(copiedRequest, (ServletResponse) response);
}
}
And you need to use this Filter in either web.xml or WebApplicationInitializer

Get destination controller from a HttpServletRequest

I have set up spring security to authenticate and authorize requests coming into my application. I have set up the configuration as so:
public class OAuth2ServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// ...set up token store here
resources.authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//QUESTION
// How do I get the destination controller that this request was going to go to?
// Really, I'd like to get some information about the annotations that were on the destination controller.
response.setStatus(401);
}
});
}
I'd like to grab some information about the destination controller that this request was going to go to. The controller isn't actually going to get hit in this scenario because spring security kicked in and threw out the response before it reached the controller.
Any tips?
Thanks!
Assuming that OAuth2ServerConfiguration is a Spring managed bean, this should work for you.
...
#Autowired
private List<HandlerMapping> handlerMappings;
for (HandlerMapping handlerMapping : handlerMappings) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
If unable to Autowire a List of HandlerMapping, Autowire ApplicationContext and adjust as follows.
for (HandlerMapping handlerMapping : applicationContext.getBeansOfType(HandlerMapping.class).values()) {
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
if (handlerExecutionChain != null) {
// handlerExecutionChain.getHandler() is your handler for this request
}
}
You could try this:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// handler is the controller
MyAnnotation annotation = ((HandlerMethod) handler).getMethod().getAnnotation(MyAnnotation.class)
// do stuff with the annotation
}
});
}
}

Categories