5

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:

  • Request with param1=1: method completes successfully.
  • Request with param1=blah: JBWEB000120: The request sent by the client was syntactically incorrect.

However...

  • If a request is made with an additional parameter (e.g. nonexistentparam=1), there is no error.

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

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • I haven't seen the need to do this myself, and I couldn't find any documentation directly addressing it, but it seems that you may need to configure (or subclass) the container's `RequestMappingHandlerAdapter`. – chrylis -cautiouslyoptimistic- Jan 14 '14 at 12:37
  • 2
    How to distinguish between request params that are invalid from valid ones? The fact that a request parameter is not mapped to a controller param does not mean that it is an invalid request param, because there might be other components that use it, e.g. servlet filters or it is simply passed through and rendered in a hidden field again. – René Link Jan 14 '14 at 12:41
  • @chrylis The need to do this is driven by a split client/server development. I'm working on an API on the server side that is subject to change and would like the client requests to break if for example a parameter is renamed or deprecated (rather than silently ignoring them). – Steve Chambers Jan 14 '14 at 14:32
  • @SteveChambers If this is REST-based, a simpler and more reliable way to handle API changes is to version the media types that you're using. – chrylis -cautiouslyoptimistic- Jan 14 '14 at 14:54
  • @chrylis Not sure I understand exactly what you mean but let's say the above ParamModel was somehow versioned as 1.0. If a client called version 1.0 but erroneously passed in a parameter that isn't part of ParamModel, I'd like it to report an error. Not sure how versioning solves this? – Steve Chambers Jan 14 '14 at 15:29
  • @RenéLink Good point - have now changed the title to "check for *unbound* parameters" – Steve Chambers Jan 14 '14 at 15:32

5 Answers5

1

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

Ashish Jagtap
  • 2,799
  • 3
  • 30
  • 45
0

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; 
}
Jaiwo99
  • 9,687
  • 3
  • 36
  • 53
  • 2
    While Bean Validation is useful, this doesn't answer this question, which is how to get Spring to raise an error if extra HTTP parameters are sent. Bean Validation happens after they're set onto a model object. – chrylis -cautiouslyoptimistic- Jan 14 '14 at 12:30
0

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.

Community
  • 1
  • 1
Michael
  • 46
  • 4
0

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
}
Jaakko Sipari
  • 83
  • 1
  • 5
0

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.

tbrush
  • 236
  • 2
  • 3