11

I have a web service written in Spring MVC. It can be used by 3rd party developers. Our methods have a lot of optional parameters (passed in the query string).

I want to make sure that all the query string parameters are spelled correctly and there is no typos. Is there an easy way to do it? Method signature example:

 @RequestMapping(value = {"/filter"}, method = RequestMethod.GET)
    @ResponseBody
    public List<MetricType> getMetricTypes(    
            @RequestParam(value = "subject", required = false) Long subjectId,
            @RequestParam(value = "area", required = false) Long areaId,
            @RequestParam(value = "onlyImmediateChildren", required = false) Boolean onlyImmediateChildren,   
            @RequestParam(value = "componentGroup", required = false) Long componentGroupId    
            ) throws Exception
    {
        //Some code
    }

If somebody calls this method with "onlyImediateChildren=true" parameter (a typo) instead of "onlyImmediateChildren=true", Spring MVC will ignore the typoed parameter and will assume "onlyImmediateChildren" is null. Developer will get slightly incorrect list of results and will not notice the error. Such issues could be widespread and difficult to diagnose. I want to check there is no typoed params in query string to prevent such issues.

UPDATE

It is possible to extract the list of actual parameters from the query string. Then it could be compared with the list of the allowed parameters. If I hardcode the allowed parameter list, it will duplicate the method signature. I wonder if it is easy to extract a list of allowed parameters from the method signature (e.g. by @RequestParam annotation)?

Many thanks

Maxim

Maxim Eliseev
  • 3,248
  • 4
  • 29
  • 35

4 Answers4

6

You could implement your own HandlerInterceptor. In preHandle method you can obtain all HandlerMethod's parameters annotated with @RequestParameter. These will be all allowed parameters in request.

sinuhepop
  • 20,010
  • 17
  • 72
  • 107
2

Here is my implementation of an HandlerInterceptor which will only accept the parameters which are explicitely defined by a parameter annotation:

import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor

/**
 * Interceptor which assures that only expected [RequestParam]s are send.
 */
@Component
class UnexpectedParameterHandler : HandlerInterceptor {

    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        if (handler is HandlerMethod) {
            val queryParams = request.parameterNames.toList()
            val expectedParams = handler.methodParameters
                .map { methodParameter ->
                    val requestParamName = methodParameter.getParameterAnnotation(RequestParam::class.java)?.name
                    val parameterName = methodParameter.parameter.name
                    requestParamName ?: parameterName
                }

            val unknownParameters = queryParams.minus(expectedParams)
            if (unknownParameters.isNotEmpty()) {
                response.writer.write("unexpected parameter $unknownParameters")
                response.status = HttpStatus.BAD_REQUEST.value()
                return false
            }
        }

        return super.preHandle(request, response, handler)
    }
}
Tobias
  • 7,282
  • 6
  • 63
  • 85
  • Thanks! Note that this seems to have one flaw: if you pass an invalid query parameter that just happens to have the same name as a path parameter that does exist, there won't be an error. I was able to fix that by using `mapNotNull` and not falling back to `methodParameter.parameter.name`. – Gama11 Jun 08 '21 at 15:49
  • This also seems to interfere with Spring Boot's built in `BasicErrorController`. I.e. you end up with a misleading error message for query parameters that do _exist_, but whose _values_ are invalid (invalid enum value etc). I'm now checking that `request.dispatcherType == DispatcherType.REQUEST` to prevent this. – Gama11 Jun 09 '21 at 11:52
1

You could use the getParameterMap method of the request to get a Map of all the submitted parameters, and validate the keys against a list of all allowed parameters. You should be able to get the request object by simply adding it to the method signature, e.g.:

public List<MetricType> getMetricTypes(   
    HttpServletRequest request,
    @RequestParam(value = "subject", required = false) Long subjectId,
    ...
) throws Exception {
beerbajay
  • 19,652
  • 6
  • 58
  • 75
  • Thank you for that! What I am trying to do is to avoid hardcoding/duplicating a list of allowed parameters. This list is already specified by the method signature. I want to extract a list of allowed parameters from the method signature. – Maxim Eliseev Apr 04 '12 at 12:01
  • Instead of calling `request.getParameterMap` you can use a `@RequestParam Map paramMap` method parameter – soulcheck Apr 04 '12 at 12:22
  • @MaximEliseev The easiest way would be to require your clients to provide all parameters, but leave them empty when they're not necessary since then you can use `@RequestMapping`'s `params` to ensure that only the given params are present. Alternatively, you could extend `AnnotationMethodHandlerAdapter` (or some other related class; I'm not sure) to implement your parameter validation. – beerbajay Apr 04 '12 at 13:03
  • @beerbajay - an interesting idea about extending AnnotationMethodHandlerAdapter. But then I will have to get allowed params from method signature anyway. – Maxim Eliseev Apr 04 '12 at 13:06
  • A third method would be to use something like [this answer](http://stackoverflow.com/questions/442747/getting-the-name-of-the-current-executing-method/8592871#8592871) to get the current method, then using reflection to introspect the method. – beerbajay Apr 04 '12 at 13:06
  • @MaximEliseev *But then I will have to get allowed params from method signature anyway.* Isn't that what you wanted to do? Check the annotations on the parameters? – beerbajay Apr 04 '12 at 13:07
  • @beerbajay. I wanted to get actual method arguments from the request (which I know how to do) and the correct method arguments from signature (which I do not know how to do). – Maxim Eliseev Apr 11 '12 at 09:51
  • Yes, you will have to get the allowed parameters from the method signature. You do this with reflection. You can either do this **before** the method is invoked (in something like `AnnotationMethodHandlerAdapter`) or **after** (in your method itself). Your reflection code will have to inspect the method and check the annotations on each parameter. – beerbajay Apr 11 '12 at 11:14
1

Spring will inject all the query parameters present in the url string through the argument of type

@RequestParam Map<String,String> in your controller method, if present.

@RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/json"})
 public HttpEntity<PagedResources<WebProductResource>> findAll(@RequestParam Map<String, String> allRequestParams){
...

}

You can then validate the keys of the map yourself. For an "enterprisey" way to do that generically, see my answer here: How to check spring RestController for unknown query params?

mancini0
  • 4,285
  • 1
  • 29
  • 31