Classes with the @Controller
or @ControllerAdvice
annotations can have methods marked with
the @InitBinder
annotation that initialize WebDataBinder
instances, and they, in turn,
can:
-
Bind request parameters (that is, form or request data) to a model object.
-
Convert request string values (such as request parameters, path variables, headers, cookie data, etc.) to the target type of controller method arguments.
-
Format model object values as
String
values when displaying HTML forms.
Methods with the @InitBinder
annotation can register controller-specific
java.beans.PropertyEditor
or Converter
and Formatter
components from Spring.
Additionally, you can use MVC configuration to register the Converter
and Formatter
types
in a globally shared FormattingConversionService
.
Methods marked with the @InitBinder
annotation support many of the same arguments as methods with the
@RequestMapping
annotation, with the exception of arguments annotated with @ModelAttribute
(command object). Typically they are declared with a WebDataBinder argument (for registration procedures) and a void
return value. The following listing is an example:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
- Defining a method with the annotation
@InitBinder
.
@Controller
class FormController {
@InitBinder
fun initBinder(binder: WebDataBinder) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
dateFormat.isLenient = false
binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}
// ...
}
- Defining a method with the annotation
@InitBinder
.
Additionally, if you use Formatter
based configuration via the generic
FormattingConversionService
, you can reuse the same approach and register controller-specific Formatter
implementations,
as shown in the following example:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
- Defining a method with the
@InitBinder
annotation for a custom formatter.
@Controller
class FormController {
@InitBinder
protected fun initBinder(binder: WebDataBinder) {
binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
}
// ...
}
- Defining a method with the
@InitBinder
annotation for a custom formatter.
Model structure
In the context of web applications, data binding involves binding HTTP request parameters (that is, form data or request parameters) to properties of the model object and its nested objects.
For data binding, only public
properties corresponding to JavaBeans naming
conventions are exposed - for example, the public String getFirstName()
and public void
setFirstName(String)
methods for the firstName
property .
By default, Spring allows binding to all public properties in the model object graph. This means that you need to think carefully about what public properties a model has, since a client can target any public property, even those not intended for a given use case.
For example, given an HTTP form data endpoint, a malicious client could pass values for properties that exist in the model's object graph but are not part of the HTML form presented in the browser. This may result in the model object and any of its sub-objects being set to data that is not expected to be updated.
The recommended approach is to use a specialized model object that exposes only those properties that are
relevant to form submission. For example, in a form to change a user's email address, the model object must declare
a minimal set of properties, 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 specialized model object for each use case of data binding, then you
must restrict the properties that are allowed for data binding. Ideally, this can be done by
registering allowed field patterns using the setAllowedFields()
method in
WebDataBinder
.
For example, to register valid field patterns in your application, you can implement a method marked with the @InitBinder
annotation in a component with the @Controller
or @ControllerAdvice
annotation as shown
below:
@Controller
public class ChangeEmailController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}
// methods annotated with @RequestMapping, etc.
}
In addition to registering valid field patterns, you can also register disallowed field patterns using the
setDisallowedFields()
method in DataBinder
and its subclasses. However, note that the
"allow list" is safer than the "deny list". Therefore, using setAllowedFields()
should be preferred
over using setDisallowedFields()
.
Note that pattern matching of valid fields is case sensitive; while matching against prohibited field patterns is not. Additionally, a field that matches a prohibited pattern will not be accepted, even if it also matches a pattern in the allowed list.
It is very important to correctly configure the valid and invalid field templates when directly opening the domain model for data binding. Otherwise it will be a big security risk.
Additionally, it is strongly recommended not to not use types from your domain model, such as JPA or Hibernate entities, as the model object in data binding scenarios.
GO TO FULL VERSION