Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check spring RestController for unknown query params?

I have a basic rest controller taking parameters.

How can I refuse the connection if the query string contains parameters that I did not define?

@RestController
@RequestMapping("/")
public class MyRest {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @ResponseBody
    public String content(@PathVariable id, @RequestParam(value = "page", required = false) int page) {
        return id;
    }
}

localhost:8080/myapp/123?pagggge=1

Currently when calling this url, the method is executed with just the id, and the unknown paggge parameter is just neglected. Which is fine in general, but how can I validate them and in case return a HTTP status code?

like image 901
membersound Avatar asked Jan 22 '15 15:01

membersound


People also ask

How do you validate query parameters in Spring boot?

Validating a PathVariable Just as with @RequestParam, we can use any annotation from the javax. validation. constraints package to validate a @PathVariable. The default message can be easily overwritten by setting the message parameter in the @Size annotation.

How do I retrieve query parameters in a Spring boot controller?

Simply put, we can use @RequestParam to extract query parameters, form parameters, and even files from the request.

CAN REST API have query parameters?

You can use query parameters to control what data is returned in endpoint responses. The sections below describe query parameters that you can use to control the set of items and properties in responses, and the order of the items returned.

How do you use RestController annotation?

Spring RestController annotation is a convenience annotation that is itself annotated with @Controller and @ResponseBody . This annotation is applied to a class to mark it as a request handler. Spring RestController annotation is used to create RESTful web services using Spring MVC.


3 Answers

You may get all parameters incoming and handle in the way you want. Quoting spring documentation:

When an @RequestParam annotation is used on a Map<String, String> or MultiValueMap<String, String> argument, the map is populated with all request parameters.

like image 153
ametiste Avatar answered Nov 11 '22 14:11

ametiste


In your controller method, you can include an argument of type @RequestParam Map<String, String> to get access to all query parameters passed in. A generic ArgsChecker service class can be used to check whether the user passed in an invalid argument. If so, you can throw an exception, which can be handled by an @ControllerAdvice class.

@RestController
@ExposesResourceFor(Widget.class)
@RequestMapping("/widgets")
public class WidgetController {


@Autowired
ArgsChecker<Widget> widgetArgsChecker;

    @RequestMapping(value = "", method = RequestMethod.GET, produces = {"application/hal+json"})
    public HttpEntity<PagedResources<WidgetResource>> findAll(@RequestParam @ApiIgnore Map<String, String> allRequestParams, Pageable pageable, PagedResourcesAssembler pageAssembler) {
        Set<String> invalidArgs = widgetArgsChecker.getInvalidArgs(allRequestParams.keySet());
        if (invalidArgs.size() > 0) {
            throw new QueryParameterNotSupportedException("The user supplied query parameter(s) that are not supported: " + invalidArgs + " . See below for a list of query paramters that are supported by the widget endpoint.", invalidArgs, widgetArgsChecker.getValidArgs());

        }

The ArgsChecker can be defined as follows:

import com.widgetstore.api.annotation.Queryable;
import lombok.Getter;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

public class ArgsChecker<T> {
    @Getter
    private Set<String> validArgs;

    private ArgsChecker(){};

        public ArgsChecker(Class<T> someEntityClass){
    validArgs= FieldUtils.getFieldsListWithAnnotation(someEntityClass,Queryable.class)
            .stream()
            .map(Field::getName)
            .collect(Collectors.toSet());
    validArgs.add("page");
    validArgs.add("size");

}

public Set<String> getInvalidArgs(final Set<String> args){
    Set<String> invalidArgs=new HashSet<>(args);
    invalidArgs.removeAll(validArgs);
    return invalidArgs;



    }
   }

, which uses reflection to find fields which are annotated with the "@Queryable" annotation:

package com.widgetstore.api.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Queryable {
} 

Now mark the fields of your domain class which you want queryable with that annotation:

@Getter
@Setter
public class Widget {
    @Queryable 
    private String productGuid;
    @Queryable 
    private String serialNumber;

    private String manufacturer;

Now make sure the ArgsChecker bean is created at application startup:

@SpringBootApplication
public class StartWidgetApi{

public static void main(String[] args){
    SpringApplication.run(StartWidgetApi.class);
}


@Bean(name="widgetArgsChecker")
public ArgsChecker<Widget> widgetArgsChecker(){
    return new ArgsChecker<Widget>(Widget.class);
}

//Other ArgsCheckers of different types may be used by other controllers.
@Bean(name="fooArgsChecker")
public ArgsChecker<Foo> fooArgsChecker(){
    return new ArgsChecker<Foo>(Foo.class);
 }
}

Finally,

Define a @ControllerAdvice class which will listen for exceptions thrown by your application:

package com.widgetstore.api.exception;


import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@ControllerAdvice
@RequestMapping(produces = "application/json")
@ResponseBody
public class RestControllerAdvice {


    @ExceptionHandler(QueryParameterNotSupportedException.class)
    public ResponseEntity<Map<String,Object>> unrecogonizedParameter(final QueryParameterNotSupportedException e){
        Map<String,Object> errorInfo = new LinkedHashMap<>();
        errorInfo.put("timestamp",new Date());
        errorInfo.put("errorMessage",e.getMessage());
        errorInfo.put("allowableParameters",e.getValidArgs());
        return new ResponseEntity<Map<String, Object>>(errorInfo,HttpStatus.BAD_REQUEST);
    }



}

, and define the QueryParameterNotSupportedException:

import lombok.Getter;

import java.util.Set;

@Getter
public class QueryParameterNotSupportedException extends RuntimeException{
    private Set<String> invalidArgs;

    private Set<String> validArgs;

    public QueryParameterNotSupportedException(String msg, Set<String> invalidArgs, Set<String> validArgs){
        super(msg);
        this.invalidArgs=invalidArgs;
        this.validArgs=validArgs;
    }

}

Now, when the user hits /widgets?someUnknownField=abc&someOtherField=xyz he will get a json response along the lines of

 {"timestamp": 2017-01-10'T'blahblah, "errorMessage": "The user supplied query parameter(s) that are not supported: ["someUnknownField","someOtherField"]. See below for a list of allowed query parameters." ,
"allowableParameters": ["productGuid","serialNumber"]
}
like image 22
mancini0 Avatar answered Nov 11 '22 13:11

mancini0


Add HttpServletRequest request in method parameters, do

String query = request.getQueryString()

in the method body and validate that.

like image 3
eis Avatar answered Nov 11 '22 14:11

eis