11
votes

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

4
Which version of Spring are you using? - Pau Kiat Wee
Do you mean spring MVC? I use file called "Spring-webmvc.3.0.5.Release.jar. " - Maxim Eliseev

4 Answers

5
votes

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.

1
votes

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 {
1
votes

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?

1
votes

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