Document springSecurityService null during BootStrap password encoding#1207
Document springSecurityService null during BootStrap password encoding#1207jamesfredley wants to merge 2 commits intoapache:7.0.xfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Updates the plugin documentation to highlight a BootStrap seeding pitfall where the legacy beforeInsert() password-encoding pattern can store plaintext passwords because springSecurityService is null on newly constructed domain instances.
Changes:
- Add a warning in the legacy User domain example explaining why password encoding is skipped in
BootStrap.groovy. - Add a new “Password Encoding in BootStrap” section describing the cause and showing a
PasswordEncoderinjection workaround. - Expand the GORM autowiring warning with more concrete guidance and a cross-reference to the new section.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| plugin-core/docs/src/docs/tutorials/usingControllerAnnotations.adoc | Adds a prominent warning for the legacy pattern and a new BootStrap password-encoding guidance section. |
| plugin-core/docs/src/docs/domainClasses/gormAutowire.adoc | Clarifies transient service injection limitations and links readers to the BootStrap password-encoding guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| ---- | ||
| <1> The `PasswordEncoder` bean is auto-configured by the plugin and can be injected into `BootStrap` by name. | ||
| <2> Pre-encode the password before constructing the User instance. This bypasses the `beforeInsert()` hook entirely. |
There was a problem hiding this comment.
The note for <2> says pre-encoding “bypasses the beforeInsert() hook entirely”, but beforeInsert() will still run; pre-encoding just avoids relying on springSecurityService being injected to perform encoding. Please reword this to avoid implying the GORM callback is skipped.
| <2> Pre-encode the password before constructing the User instance. This bypasses the `beforeInsert()` hook entirely. | |
| <2> Pre-encode the password before constructing the `User` instance so that password encoding is handled explicitly here, rather than relying on a `beforeInsert()` hook or an injected `springSecurityService` in the domain class. |
| <1> The `PasswordEncoder` bean is auto-configured by the plugin and can be injected into `BootStrap` by name. | ||
| <2> Pre-encode the password before constructing the User instance. This bypasses the `beforeInsert()` hook entirely. | ||
|
|
||
| TIP: If you are using the `beforeInsert()` pattern, you must also guard against double-encoding. Since the password is already encoded before `save()`, the `beforeInsert()` callback will attempt to encode it again. Either check whether the password is already encoded, or remove the `beforeInsert()` hook and always encode explicitly. The recommended approach is to use the `UserPasswordEncoderListener` pattern shown above, which avoids this issue entirely. |
There was a problem hiding this comment.
This TIP states that with the beforeInsert() pattern the callback will “attempt to encode it again” after pre-encoding, but earlier in this section you explain that springSecurityService is null for newly constructed instances in BootStrap.groovy, so the beforeInsert() encoding logic would no-op (no double-encoding). Consider rephrasing to clarify the specific scenarios where double-encoding can happen (e.g., if a Spring-managed listener encodes passwords, or if the domain instance does have an injected springSecurityService).
| TIP: If you are using the `beforeInsert()` pattern, you must also guard against double-encoding. Since the password is already encoded before `save()`, the `beforeInsert()` callback will attempt to encode it again. Either check whether the password is already encoded, or remove the `beforeInsert()` hook and always encode explicitly. The recommended approach is to use the `UserPasswordEncoderListener` pattern shown above, which avoids this issue entirely. | |
| TIP: Be careful not to double-encode passwords if you also use a `beforeInsert()`-style encoding hook or a Spring-managed listener (for example, a `User` domain class with an injected `springSecurityService`, or a `UserPasswordEncoderListener` that encodes on insert/update). In those cases, the callback or listener would see the already encoded value produced in `BootStrap` and attempt to encode it again. Ensure that only one mechanism is responsible for encoding (for example, rely solely on a listener such as `UserPasswordEncoderListener`, or disable automatic encoding for users created in `BootStrap`). |
Summary
Documents a common pitfall where
springSecurityServiceisnullon freshly constructed User domain instances inBootStrap.groovy, causing passwords to be stored in plaintext when using the olderbeforeInsert()encoding pattern.Problem
When using the older User domain class pattern (with
springSecurityServiceas atransientfield and password encoding inbeforeInsert()), creating users inBootStrap.groovysilently fails to encode passwords:Domain class instances created with
neware plain Groovy objects - their transient service references are not autowired by Spring. ThespringSecurityServicefield isnull, and the safe-navigation operator (?.) inencodePassword()silently skips encoding, storing the plaintext password.This does not affect the newer
UserPasswordEncoderListenerpattern (generated since Spring Core 3.1.2), which uses a Spring-managed bean with@Autowiredand handles encoding correctly via GORM persistence events.Changes
usingControllerAnnotations.adoc(tutorial):springSecurityServicewill be null in BootStrapbootstrapPasswordEncodingsection with:PasswordEncoderinjection and pre-encodingUserPasswordEncoderListenerpatterngormAutowire.adoc(domain classes):bootstrapPasswordEncodingsection