Is there a good way within the Spring framework to detect when an incoming URL has an invalid parameter? It seems like the default behavior is to ignore unrecognized parameters. The best solution I can find involves adding a parameter mapping to all my endpoints and check that mapping against the parameters it is expecting.
For example, say I have a widget site with a collection endpoint.
#RequestMapping(value = "/widgets", method = RequestMethod.GET)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search) {
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
The "search" parameter is optional because leaving it out is a convenience allowing all widgets to be found. I support a search syntax such that the following finds widgets where the foo attribute has a value of bar
GET https://example.com/widgets?search=foo:bar
A user makes a typo
GET https://example.com/widgets?saerch=foo:bar
This fails silently. Instead of finding widgets where foo=bar, all are found. I'd like it to return a 400 error stating that the "saerch" parameter is not supported. A great answer would be some sort of strict option on RequestMapping, like the following.
#RequestMapping(value = "/widgets", method = RequestMethod.GET, paramsStrict = true)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search) {
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
As far as I know such doesn't exist. I haven't figured out a clean way to intercept the request and check for all methods (and somehow communicate which parameters are valid for each method). The best I've figured out so far is to add a parameter map and check the map against accepted parameters in every single controller method.
#RequestMapping(value = "/widgets", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<WidgetList> getWidgets(
#RequestParam(value = "search", required = false) String search,
#RequestParam Map<String, String> allRequestParams) {
validateParameters(allRequestParms);
// ...
// Get list of widgets
// ...
return new ResponseEntity<WidgetList>(widgetList, HttpStatus.OK);
}
Is there a better way to do this?
Please don't post answers about my design or how I could make the search parameter required. That's beside the point I'm trying to make with a simple example. In my real-world application there are well-designed cases where checking for invalid parameter names would be useful.
You can implement your own Servlet Filter or HandlerInterceptor to validate parameters.
Following example with Filter:
public class ParametersValidationFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (validateParameters((HttpServletRequest)request, (HttpServletResponse)response)) {
chain.doFilter(request, response);
}
}
private boolean validateParameters(HttpServletRequest request, HttpServletResponse response) {
// Check parameter names in request.getParameterNames()
/*
Invalid parameter yields response.setStatus(HttpServletReponse.SC_BAD_REQUEST)
and additional info in response body
*/
// Otherwise, validation succeeds:
return true;
}
/* Other methods */
}
Also, Filter can be configurated with init method.
This 'filter-or-interceptor' way is better due to ability to reuse as well SOLID at all.
Related
I think in terms of REST, the ID should be placed into the URL, something like:
https://example.com/module/[ID]
and then I call GET, PUT, DELETE on that URL. That's kind of clear I think. In Spring MVC controllers, I'd get the ID with #PathVariable. Works.
Now, my practical problem with Spring MVC is, that if I do this, I have to NOT include the ID as part of the form (as well), Spring emits warnings of type
Skipping URI variable 'id' since the request contains a bind value with the same name.
otherwise. And it also makes kind of sense to only send it once, right? What would you do if they don't match??
That would be fine, but I do have a custom validator for my form backing bean, that needs to know the ID! (It needs to check if a certain unique name is already being used for a different entity instance, but cannot without knowing the ID of the submitted form).
I haven't found a good way to tell the validator that ID from #PathVariable, since the validation happens even before code in my controller method is executed.
How would you solve this dilemma?
This is my Controller (modified):
#Controller
#RequestMapping("/channels")
#RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
protected ChannelService channelService;
protected ChannelEditFormValidator formValidator;
#Autowired
public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
{
this.channelService = channelService;
this.formValidator = formValidator;
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
public String editChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
{
if (channelId > 0)
{
// Populate from persistent entity
}
else
{
// Prepare form with default values
}
return "channel/admin/channel-edit";
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
try
{
// Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
if (nameChannelId != null && !nameChannelId.equals(channelId))
result.rejectValue("name", "channel:admin.f1.error.name");
}
catch (EmptyResultDataAccessException e)
{
// That's fine, new valid unique name (not so fine using an exception for this, but you know...)
}
if (result.hasErrors())
{
return "channel/admin/channel-edit";
}
// Copy properties from form to ChannelEditRequest DTO
// ...
// Save
// ...
redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
// POST-REDIRECT-GET
return "redirect:/channels/" + channelId + "/admin";
}
#InitBinder("channelForm")
protected void initBinder(WebDataBinder binder)
{
binder.setValidator(formValidator);
}
}
I think I finally found the solution.
As it turns out Spring binds path variables to form beans, too! I haven't found this documented anywhere, and wouldn't have expected it, but when trying to rename the path variable, like #DavidW suggested (which I would have expected to only have a local effect in my controller method), I realized that some things got broken, because of the before-mentioned.
So, basically, the solution is to have the ID property on the form-backing object, too, BUT not including a hidden input field in the HTML form. This way Spring will use the path variable and populate it on the form. The local #PathVariable parameter in the controller method can even be skipped.
The cleanest way to solve this, I think, is to let the database handle the duplicates: Add a unique constraint to the database column. (or JPA by adding a #UniqueConstraint)
But you still have to catch the database exception and transform it to a user friendly message.
This way you can keep the spring MVC validator simple: only validate fields, without needing to query the database.
Could you not simply disambiguate the 2 (URI template variables vs. parameters) by using a different name for your URI template variable?
#RequestMapping(value = "/{chanId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long chanId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
[...]
What ever you said is correct the correct way to design rest api is to mention the resource id in path variable if you look at some examples from the swagger now as open api you could find similar examples there
for you the correct solution would be to use a custom validator like this
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile){
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
This would make sure all the fields are validated and you dont need to pass the id in the form.
I have a huge form with around 30 parameters and I don't think it's a good idea to do what I usually do.
The form will be serialized and pass all the parameters via ajax post to spring controller.
I usually do like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public #ResponseBody
void deleteEnvironment(#RequestParam("environmentName") String environmentName, #RequestParam("imageTag") String imageTag) {
//code
}
but if I have 30 parameters I will have a huge parameter list in the function.
What is the usual and correct way to avoid this?
EDIT: What if I pass the HttpServlet request only?? The request will have all the parameters and I can simple call request.getParameters("").
There are two options I would suggest:
Take an HttpServletRequest object and fetch needed properties separately.
The problem is Spring's controllers are designed to eliminate such low-level API (Servlets API) calls. It's could be the right fit if a controller was too abstract (operates on abstract datasets), which means you wouldn't be able to define a DTO with a fixed-length number of parameters.
Construct a DTO class with the properties needed and take it as a parameter.
The advantage is you completely delegate low-level work to Spring and care only about your application logic.
You can do something like this:
#RequestMapping(value = "/save-state", method = RequestMethod.POST)
public void deleteEnvironment(#RequestBody MyData data) {
//code
}
Create a class containing all your form parameters and receive that on your method.
but if I have 30 parameters I will have a huge parameter list in the
function.
In your request, pass a JSON object that contains these information and create its counterpart in Java.
RequestMethod.POST is not design to perform deletion.
Usr rather RequestMethod.DELETE.
#RequestMapping(value = "/save-state", method = RequestMethod.DELETE)
public #ResponseBody
void deleteEnvironment(MyObject myObject) {
//code
}
Correct way is to serialize all parameters as Json and call back end api with one parameter.
On back-end side get that json and parse as objects.
Example:
` #RequestMapping(method = POST, path = "/task")
public Task postTasks(#RequestBody String json,
#RequestParam(value = "sessionId", defaultValue = "-1") Long sessionId)
throws IOException, AuthorizationException {
Task task = objectMapper.readValue(json, Task.class);
`
That defines multi route method in controller in Spring MVC
#RequestMapping(value={"/path", "/path2"}, method = RequestMethod.GET)
public String MyMethod () {
// Determine which route invoked the method
return null;
}
Is there a way to determine which route invoked the method?
Appreciate your kind help.
You could use HttpServletRequest which has a method called getRequestURL() to retrieve the actual URL, allowing you to parse which path was used.
However, another possibility is using path variables instead:
#RequestMapping(value = "/{path}", method = RequestMethod.GET)
public String myMethod(#PathVariable String path) {
// Do stuff with "path"
return null;
}
In this case, the path variable will contain whatever you enter matching the path given in your #RequestMapping, in your case it would be "path" or "path2". However, this will also allow other path variables as well ("path3" for example, ...), so you might want to validate it first before using.
I believe you can use HttpServletRequest:
#RequestMapping(value={"/path.html", "/path2.html"}, method = RequestMethod.GET)
public String MyMethod (HttpServletRequest request) {
// Determine which route invoked the method
String url = new String(request.getRequestURL());
log.debug("URL: " + url); //use whatever you use to log
return null;
}
Hi i'm trying to build my own parameter extractor + validator by using Spring AOP.
First of all, i defined a request mapping like this:
#RequestMapping(value = "/hello", method = RequestMethod.GET)
public void ttt(#MyParam(method = CheckMethod.LONG_TIMESTAMP) Long mparam) {
// do something here
System.out.println(mparam);
}
And the AOP:
#Around(value = "xxx")
public Object extractAndValidate(ProceedingJoinPoint pjp) throws Throwable {
Object[] arguments = pjp.getArgs();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
Class[] classes = method.getParameterTypes();
Annotation[][] annotations = method.getParameterAnnotations();
// get arguments whose annotation type is MyParam and get the argument name and extract it from GET request parameter or POST/PUT body
// validate all params appointed by MyParam.method, if something wrong, return user the detailed error explanation.
// if all validation has passed, then convert all parameters into appointed type, and then send them to pjp to proceed.
return pjp.proceed(arguments);
}
The big problem is Spring automatically identifies the parameter name from RequestMapping method, and trying to map(and type converting) GET parameter into corresponding parameter, even if i did not declare any Spring annotations for the parameter. For example, "/hello?mparam=jwoijf" will simply return a default tomcat 400 error describing "The request sent by the client was syntactically incorrect.", and there are WARN level debug logs which says spring cannot convert String "jwijf" into Long value. So how do I avoid this kind of Spring feature?
The reason why i don't want to use #ModelAttribute and #Valid way to extract params and validate them is:
Using ModelAttribute will make me create too many models(every request for a model).
Some model attributes are identical, some are not even if they have the same name.
Teammates will have to know how each attribute is restricted in order to create a new ModelAttribute.
In Spring Framework , AbstractWizardFormController seems deprecated. How to implement multiple pages form in the Spring MVC Framework. (I am not using webflow)
any example or pointer would help considering my limited knowledge in Spring.
A #Controller is a more flexible way to define a form / wizard. You are supposed to map methods to requests based on requested path / request parameters / request method. So instead of defining a list of views and processing the request based on some required "step" parameter, you can define the steps of your wizard as you wish (also the command object will be handled more transparently). Here's how you can get to emulate a classic AWFC functionality (this is only meant to be an example, there's a lot more you can do).
#Controller
#RequestMapping("/wizard.form")
#SessionAttributes("command")
public class WizardController {
/**
* The default handler (page=0)
*/
#RequestMapping
public String getInitialPage(final ModelMap modelMap) {
// put your initial command
modelMap.addAttribute("command", new YourCommandClass());
// populate the model Map as needed
return "initialView";
}
/**
* First step handler (if you want to map each step individually to a method). You should probably either use this
* approach or the one below (mapping all pages to the same method and getting the page number as parameter).
*/
#RequestMapping(params = "_step=1")
public String processFirstStep(final #ModelAttribute("command") YourCommandClass command,
final Errors errors) {
// do something with command, errors, request, response,
// model map or whatever you include among the method
// parameters. See the documentation for #RequestMapping
// to get the full picture.
return "firstStepView";
}
/**
* Maybe you want to be provided with the _page parameter (in order to map the same method for all), as you have in
* AbstractWizardFormController.
*/
#RequestMapping(method = RequestMethod.POST)
public String processPage(#RequestParam("_page") final int currentPage,
final #ModelAttribute("command") YourCommandClass command,
final HttpServletResponse response) {
// do something based on page number
return pageViews[currentPage];
}
/**
* The successful finish step ('_finish' request param must be present)
*/
#RequestMapping(params = "_finish")
public String processFinish(final #ModelAttribute("command") YourCommandClass command,
final Errors errors,
final ModelMap modelMap,
final SessionStatus status) {
// some stuff
status.setComplete();
return "successView";
}
#RequestMapping(params = "_cancel")
public String processCancel(final HttpServletRequest request,
final HttpServletResponse response,
final SessionStatus status) {
status.setComplete();
return "canceledView";
}
}
I tried to vary the method signatures so that you can get an idea about the flexibility I mentioned. Of course, there's a lot more to it: you can make use of request method (GET or POST) in the #RequestMapping, you can define a method annotated with #InitBinder, etc.
EDIT: I had an unmapped method which I fixed (by the way, you need to make sure you don't have ambiguous mappings -- requests that could be mapped to more than one method -- or unmapped requests -- requests that cannot be mapped to any method). Also have a look at #SessionAttributes, #SessionStatus and #ModelAttribute, which are also needed for fully simulating the behaviour of the classic AWFC (I edited the code already to make this clear).