Reputation: 14845
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?
@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;
}
}
Upvotes: 6
Views: 2863
Reputation: 44418
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.
Upvotes: 2
Reputation: 1332
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! =)
Upvotes: 2
Reputation: 40068
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.
Upvotes: 2