Customizing data binding

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.

Time for action - whitelisting form fields for binding

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:

  1. Open our ProductController class and add a method as follows:
          @InitBinder 
          public void initialiseBinder(WebDataBinder binder) { 
             binder.setAllowedFields("productId", 
                      "name", 
                      "unitPrice", 
                      "description", 
                      "manufacturer", 
                      "category", 
                      "unitsInStock", 
                      "condition"); 
          } 
    
  2. Add an extra parameter of the type BindingResult (org.springframework.validation.BindingResult) to the processAddNewProductForm method as follows:
          public String 
          processAddNewProductForm(@ModelAttribute("newProduct") 
          Product productToBeAdded, BindingResult result) 
    
  3. In the same processAddNewProductForm method, add the following condition just before calling the  productService.addProduct(newProduct)line:
  4. Now run our application and enter the URL 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.
  5. Now press the Add button and you will see a HTTP Status 500 error on the web page as shown in the following image:
    Time for action - whitelisting form fields for binding

    The add product page showing an error for disallowed fields

  6. Now open 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> 
    
  7. Now run our application again and enter the URL 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.
  8. Now enter all information related to the new product and click on the Add 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.

Tip

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

Tip

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.

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.

Pop quiz - data binding

Consider the following data binding customization and identify the possible matching field bindings:

@InitBinder 
public void initialiseBinder(WebDataBinder binder) { 
   binder.setAllowedFields("unit*"); 
} 
  1. NoOfUnit
  2. unitPrice
  3. priceUnit
  4. united
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.109.5