|
1 | | -xref:core/validation/data-binding.adoc[Data binding] for web requests involves |
2 | | -binding request parameters to a model object. By default, request parameters can be bound |
3 | | -to any public property of the model object, which means malicious clients can provide |
4 | | -extra values for properties that exist in the model object graph, but are not expected to |
5 | | -be set. This is why model object design requires careful consideration. |
| 1 | +Data binding involves binding untrusted input onto application objects. |
| 2 | +For security reasons, it's crucial to ensure that input is properly constrained to expected fields only. |
| 3 | +This section provides guidance for safe binding. |
6 | 4 |
|
7 | | -TIP: The model object, and its nested object graph is also sometimes referred to as a |
8 | | -_command object_, _form-backing object_, or _POJO_ (Plain Old Java Object). |
| 5 | +First, prefer **immutable object design** for web binding purposes. |
| 6 | +It is safe because a constructor naturally constrains binding to expected inputs. |
| 7 | +You can use a Java record or a class with a primary constructor, and either can have further nested objects. |
| 8 | +See xref:core/validation/data-binding.adoc#data-binding-constructor-binding[Constructor Binding] for details. |
9 | 9 |
|
10 | | -A good practice is to use a _dedicated model object_ rather than exposing your domain |
11 | | -model such as JPA or Hibernate entities for web data binding. For example, on a form to |
12 | | -change an email address, create a `ChangeEmailForm` model object that declares only |
13 | | -the properties required for the input: |
| 10 | +Another option for safe binding is to use **dedicated objects** designed for the expected input. |
| 11 | +Such objects, even if mutable, are safe because they constrain binding to the expected inputs. |
14 | 12 |
|
15 | | -[source,java,indent=0,subs="verbatim,quotes"] |
16 | | ----- |
17 | | - public class ChangeEmailForm { |
18 | | -
|
19 | | - private String oldEmailAddress; |
20 | | - private String newEmailAddress; |
21 | | -
|
22 | | - public void setOldEmailAddress(String oldEmailAddress) { |
23 | | - this.oldEmailAddress = oldEmailAddress; |
24 | | - } |
25 | | -
|
26 | | - public String getOldEmailAddress() { |
27 | | - return this.oldEmailAddress; |
28 | | - } |
29 | | -
|
30 | | - public void setNewEmailAddress(String newEmailAddress) { |
31 | | - this.newEmailAddress = newEmailAddress; |
32 | | - } |
33 | | -
|
34 | | - public String getNewEmailAddress() { |
35 | | - return this.newEmailAddress; |
36 | | - } |
37 | | -
|
38 | | - } |
39 | | ----- |
40 | | - |
41 | | -Another good practice is to apply |
42 | | -xref:core/validation/data-binding.adoc#data-binding-constructor-binding[constructor binding], |
43 | | -which uses only the request parameters it needs for constructor arguments, and any other |
44 | | -input is ignored. This is in contrast to property binding which by default binds every |
45 | | -request parameter for which there is a matching property. |
46 | | - |
47 | | -If neither a dedicated model object nor constructor binding is sufficient, and you must |
48 | | -use property binding, we strongly recommend registering `allowedFields` patterns (case |
49 | | -sensitive) on `WebDataBinder` in order to prevent unexpected properties from being set. |
| 13 | +Domain objects such as JPA or Hibernate entities are generally not safe for web binding |
| 14 | +as they likely contain more properties than the expected inputs. |
| 15 | +For such cases, it's crucial to declare the properties to expose for binding. |
50 | 16 | For example: |
51 | 17 |
|
52 | 18 | [source,java,indent=0,subs="verbatim,quotes"] |
53 | 19 | ---- |
54 | 20 | @Controller |
55 | | - public class ChangeEmailController { |
| 21 | + public class PersonController { |
56 | 22 |
|
57 | 23 | @InitBinder |
58 | 24 | void initBinder(WebDataBinder binder) { |
59 | | - binder.setAllowedFields("oldEmailAddress", "newEmailAddress"); |
| 25 | + // See Javadoc for supported pattern syntax |
| 26 | + binder.setAllowedFields("firstName", "lastName", "*Address"); |
60 | 27 | } |
61 | | -
|
62 | | - // @RequestMapping methods, etc. |
63 | | -
|
64 | 28 | } |
65 | 29 | ---- |
66 | 30 |
|
67 | | -You can also register `disallowedFields` patterns (case insensitive). However, |
68 | | -"allowed" configuration is preferred over "disallowed" as it is more explicit and less |
69 | | -prone to mistakes. |
| 31 | +NOTE: It is also possible to configure `disallowedFields`, but that's fragile, and |
| 32 | +due to be https://github.com/spring-projects/spring-framework/issues/36802[deprecated] in Spring Framework 7.1. |
| 33 | +It is easy to miss or add others over time that should also be excluded. |
70 | 34 |
|
71 | | -By default, constructor and property binding are both used. If you want to use |
72 | | -constructor binding only, you can set the `declarativeBinding` flag on `WebDataBinder` |
73 | | -through an `@InitBinder` method either locally within a controller or globally through an |
74 | | -`@ControllerAdvice`. Turning this flag on ensures that only constructor binding is used |
75 | | -and that property binding is not used unless `allowedFields` patterns are configured. |
76 | | -For example: |
| 35 | +By default, `DataBinder` applies both constructor and setter binding. |
| 36 | +This is fine with immutable objects and dedicated objects, but for domain objects, you must |
| 37 | +remember to declare `allowFields`. To ensure data binding is only used in declarative style where |
| 38 | +expected inputs are explicitly declared, you can set `declarativeBinding=true` on `DataBinder`. |
| 39 | +In this mode, `DataBinding` applies constructor binding, and additionally setter binding if `allowedFields` is set. |
| 40 | +The below shows how to set this flag globally: |
77 | 41 |
|
78 | 42 | [source,java,indent=0,subs="verbatim,quotes"] |
79 | 43 | ---- |
80 | | - @Controller |
81 | | - public class MyController { |
| 44 | + @ControllerAdvice |
| 45 | + public class ControllerConfig { |
82 | 46 |
|
83 | 47 | @InitBinder |
84 | 48 | void initBinder(WebDataBinder binder) { |
85 | 49 | binder.setDeclarativeBinding(true); |
86 | 50 | } |
87 | | -
|
88 | | - // @RequestMapping methods, etc. |
89 | | -
|
90 | 51 | } |
91 | 52 | ---- |
0 commit comments