Since after 2 days I still cannot figure how to perform a print of a HttpServletResponse body in HandlerInterceptorAdapter, I'll ask another time :)
With HttpServletRequest I can easily do something like request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); and I have the full body but how to make the same with HttpServletResponse?
I Had found lots of question on StackOverflow about that but none of them seems to work.
This is the handler:
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//how to print here the "response" by using the "response" parameter
super.afterCompletion(request, response, handler, ex);
}
this answer is quite the same and links to this but they use ServletResponse and NOT HttpServletResponse and something with FilterChain which I have not in my afterCompletion handler. Even this that seems the most complete one is not suitable (I think) in my case.
Do someone can provide me a simple serialization example with HttpServletResponse?
It's been hard on searching deeply into it but found that ResponseBodyAdvice could be suitable for my purposes. So looking for some example on StackOverflow found this guy which had quite same issue having to manipulate the Object body.
That's my final working solution in order to implement what I wrote here
#ControllerAdvice
public class CSRFHandler implements ResponseBodyAdvice<Object> {
#Value("${security.csrf.enabled}")
private String csrfEnabled;
#Value("${security.csrf.headerName}")
private String csrfHeaderName;
#Value("${security.csrf.salt}")
private String salt;
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (new Boolean(csrfEnabled).booleanValue()) {
String csrfValue = SecureUtil.buildCsrfValue(salt, StringUtil.toJson(body));
response.getHeaders().add(csrfHeaderName, csrfValue);
}
return body;
}
}
Simple answer is "you can't do that in a Handler Interceptor".
Says so in the manual :
HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.
As a basic guideline, fine-grained handler-related preprocessing tasks are candidates for HandlerInterceptor implementations, especially factored-out common handler code and authorization checks. On the other hand, a Filter is well-suited for request content and view content handling, like multipart forms and GZIP compression. This typically shows when one needs to map the filter to certain content types (e.g. images), or to all requests.
So I advise you check out filter based solutions, as you pointed. You might be interested in : ContentCachingResponseWrapper Produces Empty Response which seems to accomplish what you want with minimal coding. But once you get started with filters, any of the well accepted answers that you have linked to in the question will likely do the job.
I ended up with the following in Kotlin:
#Bean
open fun logFilter(): CommonsRequestLoggingFilter {
val filter = InfoRequestLoggingFilter()
filter.setIncludeQueryString(true)
filter.setIncludePayload(true)
filter.setMaxPayloadLength(10000)
filter.isIncludeHeaders = false
return filter
}
import org.apache.commons.io.output.TeeOutputStream
import org.springframework.mock.web.DelegatingServletOutputStream
import org.springframework.web.filter.CommonsRequestLoggingFilter
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.PrintStream
import java.io.PrintWriter
import javax.servlet.FilterChain
import javax.servlet.ServletOutputStream
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpServletResponseWrapper
class InfoRequestLoggingFilter : CommonsRequestLoggingFilter() {
override fun beforeRequest(request: HttpServletRequest, message: String) {
logger.info(message)
}
override fun afterRequest(request: HttpServletRequest, message: String) {
// logger.info(message) - NOP, since doFilterInternal is logging it instead
}
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val outputStream = ByteArrayOutputStream()
val printStream = PrintStream(outputStream)
filterChain.doFilter(request, object : HttpServletResponseWrapper(response) {
#Throws(IOException::class)
override fun getOutputStream(): ServletOutputStream {
return DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream)
)
}
#Throws(IOException::class)
override fun getWriter(): PrintWriter {
return PrintWriter(DelegatingServletOutputStream(TeeOutputStream(super.getOutputStream(), printStream))
)
}
})
logger.info(String.format("%s ;status=%s ;payload=%s", createMessage(request, "", ""), response.status, outputStream.toString()))
}
}
Related
I am logging the request body if some custom exception occurs. For this, I have added a filter which caches the HttpServletRequest object using the ContentCachingRequestWrapper. Here is the code sample for that:
#Component
public class RequestWrapperFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
filterChain.doFilter(new ContentCachingRequestWrapper(httpServletRequest), httpServletResponse);
}
}
And, my controller class looks something like this:
#PostMapping(Paths.INSPECTION_IMAGE_SUBMIT_REQUEST_V1)
public ResponseEntity<ResponseObject> processThisRequest(
#RequestBody RequestObject requestObject //how am I able to access this requestObject?
) throws Exception {
return someService.process(requestBody);
}
So, how am I able to use this requestObject? Since, in the filter class, I have cached this, therefore, the getReader() method must have already been called on this, right? So I should not be able to read the requestObject, right?
======EDIT======
Look at how ContentCachingRequestWrapper works
javax.servlet.http.HttpServletRequest wrapper that caches all content read from the input stream and reader, and allows this content to be retrieved via a byte array.
Cached does not mean it read the input stream and save in memory, but whenever bytes is read from the wrapped stream, the cacher will write same bytes to its internal buffer.
So your requestObject is only read once in the entire filter chain, when you create the ContentCachingRequestWrapper in your filter, it does not cache anything yet.
Other part of framework will read the cached content by invoking the ContentCachingRequestWrapper#getContentAsByteArray method (not read from the stream again, as it already read). (Note that the content only available after the body reader finish reading the input stream, if you invoke before, you will receive incomplete data)
======END EDIT======
Can use RequestBodyAdviceAdapter
#ControllerAdvice
public class RequestAdvice extends RequestBodyAdviceAdapter {
#Override
#Nonnull
public Object afterBodyRead(#Nonnull Object body, #Nonnull HttpInputMessage inputMessage,
#Nonnull MethodParameter parameter, #Nonnull Type targetType,
#Nonnull Class<? extends HttpMessageConverter<?>> converterType) {
// Do something with the payload
return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
}
Try this solution . As long as you are passing wrappedRequest (as mentioned in this link) object ahead for filter chain, you can read requestObject in your controller.
I am trying to log (just to console write now for simplicity sake) the final rendered HTML that will be returned by the HttpServletResponse. (i.e. the body) To this end, I am using the HandlerInterceptorAdapter from Spring MVC like so:
public class VxmlResponseInterceptor extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(response.toString());
}
}
This works as expected and I see the HTTP response headers in the console. My question is if there is a relatively simple way to log the entire response body (i.e. final rendered HTML) to the console without having to resort to doing jumping jacks with PrintWriters, OutputStream's and the like.
Thanks in advance.
This would be better done using a Servlet Filter rather than a Spring HandlerInterceptor, for the reason that a Filter is allowed to substitute the request and/or response objects, and you could use this mechanism to substitute the response with a wrapper which logs the response output.
This would involve writing a subclass of HttpServletResponseWrapper, overriding getOutputStream (and possibly also getWriter()). These methods would return OutputStream/PrintWriter implementations that siphon off the response stream into a log, in addition to sending to its original destination. An easy way to do this is using TeeOutputStream from Apache Commons IO, but it's not hard to implement yourself.
Here's an example of the sort of thing you could do, making use of Spring's GenericFilterBean and DelegatingServletResponseStream, as well as TeeOutputStream, to make things easier:
public class ResponseLoggingFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse responseWrapper = loggingResponseWrapper((HttpServletResponse) response);
filterChain.doFilter(request, responseWrapper);
}
private HttpServletResponse loggingResponseWrapper(HttpServletResponse response) {
return new HttpServletResponseWrapper(response) {
#Override
public ServletOutputStream getOutputStream() throws IOException {
return new DelegatingServletOutputStream(
new TeeOutputStream(super.getOutputStream(), loggingOutputStream())
);
}
};
}
private OutputStream loggingOutputStream() {
return System.out;
}
}
This logs everything to STDOUT. If you want to log to a file, it'll get a big more complex, what with making sure the streams get closed and so on, but the principle remains the same.
If you're using (or considering) logback as your logging framework, there is a nice servlet filter already available that does exactly that. Checkout the TeeFilter chapter in the documentation.
I've been looking for a way to log full HTTP Request/Response for a while and discovered it has been solved for me in the Tomcat 7 RequestDumperFilter. It works as advertised from a Tomcat 7 container. If you want to use it in Jetty, the class works fine stand-alone or, as I did, copied and adapted to the specific needs of my environment.
I made a small library spring-mvc-logger available via maven central.
Add to pom.xml:
<dependency>
<groupId>com.github.isrsal</groupId>
<artifactId>spring-mvc-logger</artifactId>
<version>0.2</version>
</dependency>
Add to web.xml:
<filter>
<filter-name>loggingFilter</filter-name>
<filter-class>com.github.isrsal.logging.LoggingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Add to log4j.xml:
<logger name="com.github.isrsal.logging.LoggingFilter">
<level value="DEBUG"/>
</logger>
the code pasted below works with my tests and can be downloaded from my github project, sharing after applying a solution based on that on a production project
#Configuration
public class LoggingFilter extends GenericFilterBean {
/**
* It's important that you actually register your filter this way rather then just annotating it
* as #Component as you need to be able to set for which "DispatcherType"s to enable the filter
* (see point *1*)
*
* #return
*/
#Bean
public FilterRegistrationBean<LoggingFilter> initFilter() {
FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new LoggingFilter());
// *1* make sure you sett all dispatcher types if you want the filter to log upon
registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
// *2* this should put your filter above any other filter
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ContentCachingRequestWrapper wreq =
new ContentCachingRequestWrapper(
(HttpServletRequest) request);
ContentCachingResponseWrapper wres =
new ContentCachingResponseWrapper(
(HttpServletResponse) response);
try {
// let it be ...
chain.doFilter(wreq, wres);
// makes sure that the input is read (e.g. in 404 it may not be)
while (wreq.getInputStream().read() >= 0);
System.out.printf("=== REQUEST%n%s%n=== end request%n",
new String(wreq.getContentAsByteArray()));
// Do whatever logging you wish here, in this case I'm writing request
// and response to system out which is probably not what you wish to do
System.out.printf("=== RESPONSE%n%s%n=== end response%n",
new String(wres.getContentAsByteArray()));
// this is specific of the "ContentCachingResponseWrapper" we are relying on,
// make sure you call it after you read the content from the response
wres.copyBodyToResponse();
// One more point, in case of redirect this will be called twice! beware to handle that
// somewhat
} catch (Throwable t) {
// Do whatever logging you whish here, too
// here you should also be logging the error!!!
throw t;
}
}
}
All requests and responses handled by our Spring Rest Controller has a Common section which has certain values:
{
"common": {
"requestId": "foo-bar-123",
"otherKey1": "value1",
"otherKey2": "value2",
"otherKey3": "value3"
},
...
}
Currently all my controller functions are reading the common and copying it into the response manually. I would like to move it into an interceptor of some sort.
I tried to do this using ControllerAdvice and ThreadLocal:
#ControllerAdvice
public class RequestResponseAdvice extends RequestBodyAdviceAdapter
implements ResponseBodyAdvice<MyGenericPojo> {
private ThreadLocal<Common> commonThreadLocal = new ThreadLocal<>();
/* Request */
#Override
public boolean supports(
MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return MyGenericPojo.class.isAssignableFrom(methodParameter.getParameterType());
}
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = (MyGenericPojo)body.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
commonThreadLocal(common);
return body;
}
/* Response */
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return MyGenericPojo.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
body.setCommon(commonThreadLocal.get());
commonThreadLocal.remove();
return body;
}
}
This works when I test sending one request at a time. But, is it guaranteed that afterBodyRead and beforeBodyWrite is called in the same thread, when multiple requests are coming?
If not, or even otherwise, what is the best way of doing this?
I think that there is no need of your own ThreadLocal you can use request attributes.
#Override
public Object afterBodyRead(
Object body,
HttpInputMessage inputMessage,
MethodParameter parameter,
Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
var common = ((MyGenericPojo) body).getCommon();
if (common.getRequestId() == null) {
common.setRequestId(generateNewRequestId());
}
Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes::getRequest)
.ifPresent(request -> {request.setAttribute(Common.class.getName(), common);});
return body;
}
#Override
public MyGenericPojo beforeBodyWrite(
MyGenericPojo body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.map(rc -> rc.getAttribute(Common.class.getName(), RequestAttributes.SCOPE_REQUEST))
.ifPresent(o -> {
Common common = (Common) o;
body.setCommon(common);
});
return body;
}
EDIT
Optionals can be replaced with
RequestContextHolder.getRequestAttributes().setAttribute(Common.class.getName(),common,RequestAttributes.SCOPE_REQUEST);
RequestContextHolder.getRequestAttributes().getAttribute(Common.class.getName(),RequestAttributes.SCOPE_REQUEST);
EDIT 2
About thread safety
1) standard servlet-based Spring web application we have thread-per-request scenario. Request is processed by one of the worker threads through all the filters and routines. The processing chain will be executed by the very same thread from start to end . So afterBodyRead and beforeBodyWrite guaranteed to be executed by the very same thread for a given request.
2) Your RequestResponseAdvice by itself is stateless. We used RequestContextHolder.getRequestAttributes() which is ThreadLocal and declared as
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
And ThreadLocal javadoc states:
his class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via
its get or set method) has its own, independently initialized copy of
the variable.
So I don't see any thread-safety issues into this sulotion.
Quick answer: RequestBodyAdvice and ResponseBodyAdvice are invoked within the same thread for one request.
You can debug the implementation at: ServletInvocableHandlerMethod#invokeAndHandle
The way you're doing it is not safe though:
ThreadLocal should be defined as static final, otherwise it's similar to any other class property
Exception thrown in body will skip invocation of ResponseBodyAdvice (hence the threadlocal data is not removed)
"More safe way": Make the request body supports any class (not just MyGenericPojo), in the afterBodyRead method:
First call ThreadLocal#remove
Check if type is MyGenericPojo then set the common data to threadlocal
Also I have already answered this thread, but I prefer another way to solve such kind of problems
I would use Aspect-s in this scenario.
I have written included this in one file but you should create proper separate classes.
#Aspect
#Component
public class CommonEnricher {
// annotation to mark methods that should be intercepted
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface EnrichWithCommon {
}
#Configuration
#EnableAspectJAutoProxy
public static class CommonEnricherConfig {}
// Around query to select methods annotiated with #EnrichWithCommon
#Around("#annotation(com.example.CommonEnricher.EnrichWithCommon)")
public Object enrich(ProceedingJoinPoint joinPoint) throws Throwable {
MyGenericPojo myGenericPojo = (MyGenericPojo) joinPoint.getArgs()[0];
var common = myGenericPojo.getCommon();
if (common.getRequestId() == null) {
common.setRequestId(UUID.randomUUID().toString());
}
//actual rest controller method invocation
MyGenericPojo res = (MyGenericPojo) joinPoint.proceed();
//adding common to body
res.setCommon(common);
return res;
}
//example controller
#RestController
#RequestMapping("/")
public static class MyRestController {
#PostMapping("/test" )
#EnrichWithCommon // mark method to intercept
public MyGenericPojo test(#RequestBody MyGenericPojo myGenericPojo) {
return myGenericPojo;
}
}
}
We have here an annotation #EnrichWithCommon which marks endpoints where enrichment should happen.
If it's only a meta data that you copy from the request to the response, you can do one of the followings:
1- store the meta in the request/response header,and just use filters to do the copy :
#WebFilter(filterName="MetaDatatFilter", urlPatterns ={"/*"})
public class MyFilter implements Filter{
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("metaData", httpServletRequest.getHeader("metaData"));
}
}
2- move the work into the service layer where you can do the cope through a reusable common method, or have it run through AOP
public void copyMetaData(whatEverType request,whatEverType response) {
response.setMeta(request.getMeta);
}
I added an async endpoint to a existing spring-mvc application:
#RestController
public class MyController {
#PostMapping("/")
public Mono<String> post(Object body) {
return Mono.just("test");
//webClient.retrieve().bodyToMono(String.class);
}
}
I want to create a global interceptor/filter that will log the request body payload. But how can I get access to it?
I tried adding a HandlerInterceptorAdapter, but the payload is always empty:
static class LoggingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
byte[] buf = wrapper.getContentAsByteArray();
System.out.println(buf);
System.out.println(buf.length);
return true;
}
}
Maybe the payload is not yet present in the request, or has already been read. So how can I access the body in this async case?
Unfortunately in Webflux you cannot use HandlerInterceptorAdapter because it came from web mvc module and works only with the servlets.
I found a good article with solutions.
P.S. You must to remove spring-mvc dependencies if going to use reactive endpoins.
Is there with Spring (boot) a way to check if a REST request contains a parameter not explicitly declared by the called REST method?
With the required flag we can force the client to include a certain parameter in the request. I am looking for a similar way to disallow the client to send a parameter that is not explicity mentioned in the declaration of the controller method:
#RequestMapping("/hello")
public String hello(#RequestParam(value = "name") String name) {
//throw an exception if a REST client calls this method and
// sends a parameter with a name other than "name"
//otherwise run this method's logic
}
For example calling
curl "localhost:8080/hello?name=world&city=London"
should result in a 4xx answer.
One option would be to explicitly check for unexpected parameters:
#RequestMapping("/hello")
public String hello(#RequestParam Map<String,String> allRequestParams) {
//throw an exception if allRequestParams contains a key that we cannot process here
//otherwise run this method's logic
}
But is it also possible to achieve the same result while keeping the same convenient #RequestParam usage as in the first example?
EDIT: Sorry, I do not see any connection to this question. The other question is about annotation processing at runtime. My question is about the behaviour of Spring's REST engine. Am I missing something?
EDIT: Based on the answers, I have written this HandlerInterceptor:
#Component
public class TooManyParamatersHandlerInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod m = (HandlerMethod) handler;
if (m.getMethod().getName().equals("error")) {
return true;
}
List<String> allowedParameters = Stream.of(m.getMethodParameters())
.flatMap(p -> Stream.of(p.getParameterAnnotation(RequestParam.class)))
.filter(Objects::nonNull)
.map(RequestParam::name).collect(Collectors.toList());
ArrayList<String> actualParameters = Collections.list(request.getParameterNames());
actualParameters.removeAll(allowedParameters);
if (!actualParameters.isEmpty()) {
throw new org.springframework.web.bind.ServletRequestBindingException(
"unexpected parameter: " + actualParameters);
}
return true;
}
}
In this case you required HandlerInterceptor or HandlerInterceptorAdapter, override the preHandle method
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//request param validation validation
return true; //or throw exception
}
ServletRequest.getParameterMap() returns a map of key-values of the request parameters.
You can do it by ContainerRequestFilter feature which is added from JavaEE 7 that lets you access the resource class and resource method matched by the current request and make you to do your desire action when that have not been matched.
You can read more here :
https://docs.oracle.com/javaee/7/api/javax/ws/rs/container/ResourceInfo.html
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.Provider;
#Provider
public class RequestParamFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Set<String> acceptedParamList = new HashSet<String>();
Method method = resourceInfo.getResourceMethod();
for (Annotation[] annos : method.getParameterAnnotations()) {
for (Annotation anno : annos) {
if (anno instanceof QueryParam) {
acceptedParamList.add(((QueryParam) anno).value());
}
}
}
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
for (String param : queryParams .keySet()) {
if (!acceptedParamList.contains(param)) {
requestContext.abortWith(Response.status(Status.BAD_REQUEST).entity("Unexpected paramter found : "+param).build());
}
}
}
}
P.N : Filters are cost in your application speed most of the times, Specially if you have complex chains in it!
I recommend to use it in this case (and similar cases) because of most of the those requests should not be reached to the server application at all.
I hope this helps you and Happy coding! =)
As far as I know, you cannot simply disallow parameters using Spring. Honestly, this issue is rather questionable and unnecessary and I think it's an antipattern.
However, Spring provides with each mapping the HttpServletRequest and HttpServletResponse objects to the controller method signature. Use the method HttpServletRequest::getParameterMap to receive the Map of the passed parameters for the further iteration and validation.
#RequestMapping("/hello")
public String hello(RequestParam(value = "name") String name, HttpServletRequest request, HttpServletResponse response) {
final Map<String, String[]> parameterMap = request.getParameterMap();
// logics
}
Passing those object to only to the #RequestMapping("/hello") allows performing the validation only to the selected mapping. If you want to define this behavior globally, I suggest you use HandlerInterceptor::preHandle as answered here.
If you make the hello parameter required=true, then you can just check the size of the Map whether is equal to 1 or not.