diff --git a/framework-docs/modules/ROOT/nav.adoc b/framework-docs/modules/ROOT/nav.adoc index 928cc04de17..e14e12e840e 100644 --- a/framework-docs/modules/ROOT/nav.adoc +++ b/framework-docs/modules/ROOT/nav.adoc @@ -198,6 +198,7 @@ *** xref:web/webmvc/mvc-uri-building.adoc[] *** xref:web/webmvc/mvc-ann-async.adoc[] *** xref:web/webmvc/mvc-range.adoc[] +*** xref:web/webmvc/mvc-data-binding.adoc[] *** xref:web/webmvc-cors.adoc[] *** xref:web/webmvc-versioning.adoc[] *** xref:web/webmvc/mvc-ann-rest-exceptions.adoc[] @@ -295,6 +296,7 @@ *** xref:web/webflux-functional.adoc[] *** xref:web/webflux/uri-building.adoc[] *** xref:web/webflux/range.adoc[] +*** xref:web/webflux/data-binding.adoc[] *** xref:web/webflux-cors.adoc[] *** xref:web/webflux-versioning.adoc[] *** xref:web/webflux/ann-rest-exceptions.adoc[] diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc index e2a555fa8c5..5b1371a6e43 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-initbinder.adoc @@ -107,7 +107,4 @@ Kotlin:: [[webflux-ann-initbinder-model-design]] -== Model Design -[.small]#xref:web/webmvc/mvc-controller/ann-initbinder.adoc#mvc-ann-initbinder-model-design[See equivalent in the Servlet stack]# - -include::partial$web/web-data-binding-model-design.adoc[] +NOTE: For more guidance on model design, please see xref:web/webflux/data-binding.adoc[Data Binding]. diff --git a/framework-docs/modules/ROOT/pages/web/webflux/data-binding.adoc b/framework-docs/modules/ROOT/pages/web/webflux/data-binding.adoc new file mode 100644 index 00000000000..7f523c85f2d --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webflux/data-binding.adoc @@ -0,0 +1,36 @@ +[[webflux-data-binding]] += Data Binding +:page-section-summary-toc: 1 + +[.small]#xref:web/webmvc/mvc-data-binding.adoc[See equivalent in the Servlet stack]# + +Data binding is a mechanism that binds string parameters onto an object graph with type conversion. +It is a core mechanism of the Spring Framework that helps with application configuration. +In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values. + +To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion, +and more, see the xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section. + +For annotated controllers, data binding applies to a +xref:web/webflux/controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument. +For functional endpoints, use the `bind` method of xref:web/webflux-functional.adoc#webflux-fn-request[ServerRequest]. + +TIP: For browser applications with annotated controllers, you can use +xref:web/webflux/controller/ann-modelattrib-methods.adoc[@ModelAttribute methods] +to initialize additional model attributes for use in rendered views. + +Each request uses a separate `WebDataBinder` instance. +For annotated controllers, this instance can be customized through +xref:web/webflux/controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or +across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice]. +For functional endpoints, use overloaded `ServerRequest.bind` methods. + + + + +[[webflux-data-binding-design]] +== Model Design +[.small]#xref:web/webmvc/mvc-data-binding.adoc#mvc-data-binding-design[See equivalent in the Servlet stack]# + +include::partial$web/web-data-binding-model-design.adoc[] + diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc index 5a00ce82fd0..86a65b471d1 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-initbinder.adoc @@ -107,7 +107,4 @@ Kotlin:: [[mvc-ann-initbinder-model-design]] -== Model Design -[.small]#xref:web/webflux/controller/ann-initbinder.adoc#webflux-ann-initbinder-model-design[See equivalent in the Reactive stack]# - -include::partial$web/web-data-binding-model-design.adoc[] +NOTE: For more guidance on model design, please see xref:web/webmvc/mvc-data-binding.adoc[Data Binding]. \ No newline at end of file diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-data-binding.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-data-binding.adoc new file mode 100644 index 00000000000..63648d2be59 --- /dev/null +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-data-binding.adoc @@ -0,0 +1,34 @@ +[[mvc-data-binding]] += Data Binding +:page-section-summary-toc: 1 + +[.small]#xref:web/webflux/data-binding.adoc[See equivalent in the Reactive stack]# + +Data binding is a mechanism that binds string parameters onto an object graph with type conversion. +It is a core mechanism of the Spring Framework that helps with application configuration. +In web applications it makes it easy to access query parameters and form data through richly typed objects rather than through maps of string values. + +To learn more about the data binding mechanism, including constructor and setter binding, property name syntax, type conversion, +and more, see the xref:core/validation/data-binding.adoc[Data binding] in the Core Technologies section. + +For annotated controllers, data binding applies to a +xref:web/webmvc/mvc-controller/ann-methods/modelattrib-method-args.adoc[@ModelAttribute] method argument. +For functional endpoints, use the `bind` method of xref:web/webmvc-functional.adoc#webmvc-fn-request[ServerRequest]. + +TIP: For browser applications with annotated controllers, you can use +xref:web/webmvc/mvc-controller/ann-modelattrib-methods.adoc[@ModelAttribute methods] +to initialize additional model attributes for use in rendered views. + +Each request uses a separate `WebDataBinder` instance. +For annotated controllers, this instance can be customized through +xref:web/webmvc/mvc-controller/ann-initbinder.adoc[@InitBinder methods] within a controller, or +across controllers through xref:web/webmvc/mvc-controller/ann-advice.adoc[Controller Advice]. +For functional endpoints, use overloaded `ServerRequest.bind` methods. + + + +[[mvc-data-binding-design]] +== Model Design +[.small]#xref:web/webflux/data-binding.adoc#webflux-data-binding-design[See equivalent in the Reactive stack]# + +include::partial$web/web-data-binding-model-design.adoc[] diff --git a/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc b/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc index 6300e15f645..bd29449c4b2 100644 --- a/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc +++ b/framework-docs/modules/ROOT/partials/web/web-data-binding-model-design.adoc @@ -1,91 +1,41 @@ -xref:core/validation/data-binding.adoc[Data binding] for web requests involves -binding request parameters to a model object. By default, request parameters can be bound -to any public property of the model object, which means malicious clients can provide -extra values for properties that exist in the model object graph, but are not expected to -be set. This is why model object design requires careful consideration. +Data binding involves binding untrusted input onto application objects. +For security reasons, it's crucial to ensure that input is properly constrained to expected fields only. +This section provides guidance for safe binding. -TIP: 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). +First, prefer **immutable object design** for web binding purposes. +It is safe because a constructor naturally constrains binding to expected inputs. +You can use a Java record or a class with a primary constructor, and either can have further nested objects. +See xref:core/validation/data-binding.adoc#data-binding-constructor-binding[Constructor Binding] for details. -A good practice is to use a _dedicated model object_ rather than exposing your domain -model such as JPA or Hibernate entities for web data binding. For example, on a form to -change an email address, create a `ChangeEmailForm` model object that declares only -the properties required for the input: +Another option for safe binding is to use **dedicated objects** designed for the expected input. +Such objects, even if mutable, are safe because they constrain binding to the expected inputs. -[source,java,indent=0,subs="verbatim,quotes"] ----- - 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; - } - - } ----- - -Another good practice is to apply -xref:core/validation/data-binding.adoc#data-binding-constructor-binding[constructor binding], -which uses only the request parameters it needs for constructor arguments, and any other -input is ignored. This is in contrast to property binding which by default binds every -request parameter for which there is a matching property. - -If neither a dedicated model object nor constructor binding is sufficient, and you must -use property binding, we strongly recommend registering `allowedFields` patterns (case -sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set. +Domain objects such as JPA or Hibernate entities are generally not safe for web binding +as they likely contain more properties than the expected inputs. +For such cases, it's crucial to declare the properties to expose for binding. For example: [source,java,indent=0,subs="verbatim,quotes"] ---- @Controller - public class ChangeEmailController { + public class PersonController { @InitBinder void initBinder(WebDataBinder binder) { - binder.setAllowedFields("oldEmailAddress", "newEmailAddress"); + // See Javadoc for supported pattern syntax + binder.setAllowedFields("firstName", "lastName", "*Address"); } - - // @RequestMapping methods, etc. - } ---- -You can also register `disallowedFields` patterns (case insensitive). However, -"allowed" configuration is preferred over "disallowed" as it is more explicit and less -prone to mistakes. -By default, constructor and property binding are both used. If you want to use -constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder` -through an `@InitBinder` method either locally within a controller or globally through an -`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used -and that property binding is not used unless `allowedFields` patterns are configured. -For example: -[source,java,indent=0,subs="verbatim,quotes"] ----- - @Controller - public class MyController { +NOTE: It is also possible to configure `disallowedFields`, but that's fragile, and +due to be https://github.com/spring-projects/spring-framework/issues/36802[deprecated] in Spring Framework 7.1. +It is easy to miss or add others over time that should also be excluded. - @InitBinder - void initBinder(WebDataBinder binder) { - binder.setDeclarativeBinding(true); - } - - // @RequestMapping methods, etc. - - } ----- +By default, `DataBinder` applies both constructor and setter binding. +This is fine with immutable objects and dedicated objects, but for domain objects, you must +remember to declare `allowFields`. To ensure data binding is only used in declarative style where +expected inputs are explicitly declared, you can set `declarativeBinding=true` on `DataBinder`. +In this mode, `DataBinding` applies constructor binding, and additionally setter binding if `allowedFields` is set. \ No newline at end of file