Steve Chambers
Steve Chambers

Reputation: 39424

How to check for unbound request parameters in a Spring MVC controller method?

Given a Spring-MVC controller method:

@RequestMapping(value = "/method")
public void method(ParamModel params) { /*...*/ }

with model class:

public class ParamModel { public int param1; }

The following two results are as expected/desired:

However...

Is there a way to ensure the request is validated and rejected if it includes any parameters that aren't part of this API?

Upvotes: 5

Views: 3076

Answers (5)

tbrush
tbrush

Reputation: 246

There is a way to override controllers request methods invocations:

@Bean
public WebMvcRegistrations mvcRegistrations() {
  return new WebMvcRegistrationsAdapter() {
    @Override
    public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
      return new RequestMappingHandlerAdapter() {
        private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

        @Override
        protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
          return new ServletInvocableHandlerMethod(handlerMethod) {
            Set<String> boundParametersNames = Stream.of(getMethodParameters())
                .map(methodParameter -> {
                  methodParameter.initParameterNameDiscovery(parameterNameDiscoverer);
                  return methodParameter.getParameterName();
                })
                .collect(Collectors.toSet());

            @Override
            public Object invokeForRequest(NativeWebRequest request,
                                           ModelAndViewContainer mavContainer,
                                           Object... providedArgs) throws Exception {
              for (Iterator<String> iterator = request.getParameterNames(); iterator.hasNext(); ) {
                String parameterName = iterator.next();
                if (!boundParametersNames.contains(parameterName)) {
                  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
                }
              }
              return super.invokeForRequest(request, mavContainer, providedArgs);
            }
          };
        }
      };
    }
  };
}

In a InvocableHandlerMethod both request parameters and method parameters can be easily accessed and verified.

Upvotes: 0

Jaakko Sipari
Jaakko Sipari

Reputation: 93

The most obvious, boring and non-Springish option would be to use:

@RequestParam Map<String,String> allRequestParams

... and check the list of parameters for yourself. It of course requires you to parse (to integer, etc.) and validate the values manually instead of using a DTO and/or javax.validation annotations.

Full example (requires mapping the InvalidParamsException to a status code):

@GetMapping("/my_strict_api")
public void myStrictApi(@RequestParam Map<String,String> allRequestParams) {
  Set<String> allowed = new HashSet<>(Arrays.asList("cat", "dog"));
  if (!allowed.containsAll(allRequestParams.keySet())) {
    throw new InvalidParamsException("We only accept: " + allowed.toString());
  }
  // You should also validate the parameter values before using them
}

Upvotes: 0

Michael
Michael

Reputation: 46

Spring's @RequestMapping takes a "params" parameter.

Documentation:

The parameters of the mapped request, narrowing the primary mapping.

Same format for any environment: a sequence of "myParam=myValue" style expressions, with a request only mapped if each such parameter is found to have the given value. Expressions can be negated by using the "!=" operator, as in "myParam!=myValue". "myParam" style expressions are also supported, with such parameters having to be present in the request (allowed to have any value). Finally, "!myParam" style expressions indicate that the specified parameter is not supposed to be present in the request.

Another possiblity is to use PathVariable (always required) or RequestParam with parameter required=true.

Update:

You could create your own request mapping conditions by subclassing RequestMappingHandlerMapping and overriding getCustomMethodCondition/getCustomTypeCondition.

However the XML configuration <mvc:annotation-driven/> cannot be used as that also declares this Bean and you would end up with 2 handler mappings. Look at Adding custom RequestCondition's in Spring mvc 3.1 for details.

Upvotes: 0

Ashish Jagtap
Ashish Jagtap

Reputation: 2819

You can use filter to check for invalid parameters as

web.xml

<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.mypackage.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

MyFilter Class

import javax.servlet.Filter;
public class MyFilter implements Filter {

    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        String requestUrl = request.getParameter("param1");
    //here I am considering 'param1=1' as valid request rest of all are invalid
             if(!requestUrl.equals("1")) {
        logger.info("Invalid Request"); 
        //for invalid request redirect to error or login page
        response.sendRedirect("/error"");           
    } else {
        logger.info("Valid Request");   
    }
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }       

}

hope this will solve your problem

Upvotes: 1

Jaiwo99
Jaiwo99

Reputation: 10017

A good practice will be Bean-Validation (JSR-303). here is the Document

keep it simple, you need have this in your spring config:

<mvc:annotation-driven />

and you can have this in your code:

@RequestMapping(value = "/method")
public void method(@Valid ParamModel params, BindingResult result) {
    if(result.hasErrors()) {...}
    else {...}
}

public class ParamModel { 
    @SomeAnnotation // details see document 
    private int param1; 
}

Upvotes: 0

Related Questions