In the previous exercise, while adding a new product we bound every field of the Product domain in the form, but it is meaningless to specify unitsInOrder and discontinued values during the addition of a new product because nobody can make an order before adding the product to the store; similarly discontinued products need not be added in our product list. So we should not allow these fields to be bound with the form bean while adding a new product to our store. However we should only allow all the other fields of the Product domain object to be bound. Let's see how to do this with the following steps:
Open our ProductController class and add a method as follows:
1.
@InitBinder
public void initialiseBinder(WebDataBinder binder) {
binder.setAllowedFields("productId",
"name",
"unitPrice",
"description",
"manufacturer",
"category",
"unitsInStock",
"condition");
}
Add an extra parameter of the type BindingResult
2. (org.springframework.validation.BindingResult) to the
processAddNewProductForm method as follows:
public String
processAddNewProductForm(@ModelAttribute("newProduct")
Product productToBeAdded, BindingResult result)
Working with Spring Tag Libraries
[ 118 ]
In the same processAddNewProductForm method, add the following condition
3. just before calling the productService.addProduct(newProduct)line:
String[] suppressedFields = result.getSuppressedFields();
if (suppressedFields.length > 0) {
throw new RuntimeException("Attempting to bind
disallowed fields: " +
StringUtils.arrayToCommaDelimitedString(suppressedFields));
}
Now run our application and enter the URL
4.
http://localhost:8080/webstore/market/products/add. You will be able to see a web page showing a web form to add new product information. Fill out all the fields, particularly Units in order and discontinued.
Now press the Add button and you will see a HTTP Status 500 error on the web
5. page as shown in the following image:
The add product page showing an error for disallowed fields
Now open addProduct.jsp from src/main/webapp/WEB-INF/views/ in your
6. project and remove the input tags that are related to the Units in order and discontinued fields. Basically, you need to remove the following block of code:
<div class="form-group">
<label class="control-label col-lg-2"
for="unitsInOrder">Units In
Working with Spring Tag Libraries
Order</label>
<div class="col-lg-10">
<form:input id="unitsInOrder" path="unitsInOrder"
type="text" class="form:input-large"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-lg-2"
for="discontinued">Discontinued</label>
<div class="col-lg-10">
<form:checkbox id="discontinued" path="discontinued"/>
</div>
</div>
Now run our application again and enter the URL
7.
http://localhost:8080/webstore/market/products/add. You will be able to see a web page showing a web form to add a new product, but this time without the Units in order and Discontinued fields.
Now enter all information related to the new product and click on the Add
8. button. You will see the new product added in the product listing page under the URL http://localhost:8080/webstore/market/products.
What just happened?
Our intention was to put some restrictions on binding HTTP parameters with the form baking bean. As we already discussed, the automatic binding feature of Spring could lead
to a potential security vulnerability if we used a domain object itself as form bean. So we have to explicitly tell Spring MVC which are fields are allowed. That's what we are doing in step 1.
The @InitBinder annotation designates a Controller method as a hook method to do some custom configuration regarding data binding on WebDataBinder. And WebDataBinder is the thing that is doing the data binding at runtime, so we need to specify which fields are allowed to bind to WebDataBinder. If you observe our initialiseBinder method from ProductController, it has a parameter called binder, which is of the type
WebDataBinder. We are simply calling the setAllowedFields method on the binder object and passing the field names that are allowed for binding. Spring MVC will call this method to initialize WebDataBinder before doing the binding since it has the
@InitBinder annotation.
Working with Spring Tag Libraries
[ 120 ]
WebDataBinder also has a method called setDisallowedFields to
strictly specify which fields are disallowed for binding . If you use this
method, Spring MVC allows any HTTP request parameters to be bound except those field names specified in the setDisallowedFields method. This is called blacklisting binding.
Okay, we configured which fields are allowed for binding, but we need to verify whether any fields other than those allowed are bound with the form baking bean. That's what we are doing in steps 2 and 3.
We changed processAddNewProductForm by adding one extra parameter called result, which is of the type BindingResult. Spring MVC will fill this object with the result of the binding. If any attempt is made to bind any fields other than the allowed fields, the
BindingResult object will have a getSuppressedFields count greater than zero. That's why we were checking the suppressed field count and throwing a RuntimeException exception:
if (suppressedFields.length > 0) {
throw new RuntimeException("Attempting to bind disallowed fields: " +
StringUtils.arrayToCommaDelimitedString(suppressedFields));
}
Here the static class StringUtils comes from
org.springframework.util.StringUtils.
We want to ensure that our binding configuration is working; that's why we run our
application without changing the View file addProduct.jsp in step 4. And as expected,
we got the HTTP Status 500 error saying Attempting to bind disallowed fields when we submitted the Add products form with the unitsInOrder and discontinued fields filled out. Now we know our binder configuration is working, we could change our View file so
as not to bind the disallowed fields. That's what we were doing in step 6: just removing the input field elements that are related to the disallowed fields from the addProduct.jsp file.
After that, our added new products page works just fine, as expected. If any outside
attackers try to tamper with the POST request and attach a HTTP parameter with the same field name as the form baking bean, they will get a RuntimeException.
Working with Spring Tag Libraries
The whitelisting is just an example of how can we customize the binding with the help of WebDataBinder. But by using WebDataBinder, we can perform many more types of binding customization as well. For example, WebDataBinder internally uses many
PropertyEditor (java.beans.PropertyEditor) implementations to convert the HTTP request parameters to the target field of the form backing bean. We can even register custom PropertyEditor objects with WebDataBinder to convert more complex data types. For instance, look at the following code snippet that shows how to register the custom PropertyEditor to convert a Date class:
@InitBinder
public void initialiseBinder (WebDataBinder binder) {
DateFormat dateFormat = new SimpleDateFormat("MMM d, YYYY");
CustomDateEditor orderDateEditor = new CustomDateEditor(dateFormat,
true);
binder.registerCustomEditor(Date.class, orderDateEditor);
}
There are many advanced configurations we can make with WebDataBinder in terms of data binding, but for a beginner level we don't need that level of detail.