DataBinder
@Controller
or @ControllerAdvice
classes can have @InitBinder
methods that
initialize instances of WebDataBinder
, and those, in turn, can:
-
Bind request parameters (that is, form or query data) to a model object.
-
Convert String-based request values (such as request parameters, path variables, headers, cookies, and others) to the target type of controller method arguments.
-
Format model object values as
String
values when rendering HTML forms.
@InitBinder
methods can register controller-specific java.beans.PropertyEditor
or
Spring Converter
and Formatter
components. In addition, you can use the
MVC config to register Converter
and Formatter
types in a globally shared FormattingConversionService
.
@InitBinder
methods support many of the same arguments that @RequestMapping
methods
do, except for @ModelAttribute
(command object) arguments. Typically, they are declared
with a WebDataBinder
argument (for registrations) and a void
return value.
The following listing shows an example:
-
Java
-
Kotlin
@Controller
public class FormController {
@InitBinder (1)
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
1 | Defining an @InitBinder method. |
@Controller
class FormController {
@InitBinder (1)
fun initBinder(binder: WebDataBinder) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
dateFormat.isLenient = false
binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}
// ...
}
1 | Defining an @InitBinder method. |
Alternatively, when you use a Formatter
-based setup through a shared
FormattingConversionService
, you can re-use the same approach and register
controller-specific Formatter
implementations, as the following example shows:
-
Java
-
Kotlin
@Controller
public class FormController {
@InitBinder (1)
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
1 | Defining an @InitBinder method on a custom formatter. |
@Controller
class FormController {
@InitBinder (1)
protected fun initBinder(binder: WebDataBinder) {
binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
}
// ...
}
1 | Defining an @InitBinder method on a custom formatter. |
Model Design
In the context of web applications, data binding involves the binding of HTTP request parameters (that is, form data or query parameters) to properties in a model object and its nested objects.
Only public
properties following the
JavaBeans naming conventions
are exposed for data binding — for example, public String getFirstName()
and
public void setFirstName(String)
methods for a firstName
property.
The model object, and its nested object graph, is also sometimes referred to as a command object, form-backing object, or POJO (Plain Old Java Object). |
By default, Spring permits binding to all public properties in the model object graph. This means you need to carefully consider what public properties the model has, since a client could target any public property path, even some that are not expected to be targeted for a given use case.
For example, given an HTTP form data endpoint, a malicious client could supply values for properties that exist in the model object graph but are not part of the HTML form presented in the browser. This could lead to data being set on the model object and any of its nested objects, that is not expected to be updated.
The recommended approach is to use a dedicated model object that exposes only
properties that are relevant for the form submission. For example, on a form for changing
a user’s email address, the model object should declare a minimum set of properties such
as in the following ChangeEmailForm
.
public class ChangeEmailForm {
private String oldEmailAddress;
private String newEmailAddress;
public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}
public String getOldEmailAddress() {
return this.oldEmailAddress;
}
public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}
public String getNewEmailAddress() {
return this.newEmailAddress;
}
}
If you cannot or do not want to use a dedicated model object for each data
binding use case, you must limit the properties that are allowed for data binding.
Ideally, you can achieve this by registering allowed field patterns via the
setAllowedFields()
method on WebDataBinder
.
For example, to register allowed field patterns in your application, you can implement an
@InitBinder
method in a @Controller
or @ControllerAdvice
component as shown below:
@Controller
public class ChangeEmailController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}
// @RequestMapping methods, etc.
}
In addition to registering allowed patterns, it is also possible to register disallowed
field patterns via the setDisallowedFields()
method in DataBinder
and its subclasses.
Please note, however, that an "allow list" is safer than a "deny list". Consequently,
setAllowedFields()
should be favored over setDisallowedFields()
.
Note that matching against allowed field patterns is case-sensitive; whereas, matching against disallowed field patterns is case-insensitive. In addition, a field matching a disallowed pattern will not be accepted even if it also happens to match a pattern in the allowed list.
It is extremely important to properly configure allowed and disallowed field patterns when exposing your domain model directly for data binding purposes. Otherwise, it is a big security risk. Furthermore, it is strongly recommended that you do not use types from your domain model such as JPA or Hibernate entities as the model object in data binding scenarios. |