In the last section, you saw how to bind data submitted by an HTML form to a form backing bean. In order to do the binding, Spring MVC internally uses a special binding object called WebDataBinder
(org.springframework.web.bind.WebDataBinder
).
WebDataBinder
extracts the data out of the HttpServletRequest
object and converts it to a proper data format, loads it into a form backing bean, and validates it. To customize the behavior of data binding, we can initialize and configure the WebDataBinder
object in our Controller. The
@InitBinder
(org.springframework.web.bind.annotation.InitBinder
) annotation helps us to do that. The @InitBinder
annotation designates a method to initialize WebDataBinder
.
Let's look at a practical use for customizing WebDataBinder
. Since we are using the actual domain object itself as a form backing bean, during the form submission there is a chance of security vulnerabilities. Because Spring automatically binds HTTP parameters to form bean properties, an attacker could bind a suitably-named HTTP parameter with form properties that weren't intended for binding. To address this problem, we can explicitly tell Spring which fields are allowed for form binding. Technically speaking, the process of explicitly telling which fields are allowed for binding is called whitelisting binding in Spring MVC; we can do whitelisting binding using WebDataBinder
.
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:
ProductController
class and add a method as follows:@InitBinder public void initialiseBinder(WebDataBinder binder) { binder.setAllowedFields("productId", "name", "unitPrice", "description", "manufacturer", "category", "unitsInStock", "condition"); }
BindingResult
(org.springframework.validation.BindingResult
) to the processAddNewProductForm
method as follows: public String
processAddNewProductForm(@ModelAttribute("newProduct")
Product productToBeAdded, BindingResult result)
processAddNewProductForm
method, add the following condition just before calling the productService.addProduct(newProduct)
line: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
.addProduct.jsp
from src/main/webapp/WEB-INF/views/
in your 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 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>
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.http://localhost:8080/webstore/market/products
.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.
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)); }
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
.
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.
3.144.109.5