简体   繁体   中英

How to check if unsupported parameter is contained in REST request?

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;
    }
}

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());
            }
        }
    }

}

PN: 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! =)

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM