Skip to content

Document springSecurityService null during BootStrap password encoding#1207

Open
jamesfredley wants to merge 2 commits intoapache:7.0.xfrom
jamesfredley:docs/bootstrap-password-encoding
Open

Document springSecurityService null during BootStrap password encoding#1207
jamesfredley wants to merge 2 commits intoapache:7.0.xfrom
jamesfredley:docs/bootstrap-password-encoding

Conversation

@jamesfredley
Copy link
Contributor

Summary

Documents a common pitfall where springSecurityService is null on freshly constructed User domain instances in BootStrap.groovy, causing passwords to be stored in plaintext when using the older beforeInsert() encoding pattern.

Problem

When using the older User domain class pattern (with springSecurityService as a transient field and password encoding in beforeInsert()), creating users in BootStrap.groovy silently fails to encode passwords:

// In BootStrap.groovy - password will NOT be encoded
def testUser = new User(username: 'me', password: 'password').save()

Domain class instances created with new are plain Groovy objects - their transient service references are not autowired by Spring. The springSecurityService field is null, and the safe-navigation operator (?.) in encodePassword() silently skips encoding, storing the plaintext password.

This does not affect the newer UserPasswordEncoderListener pattern (generated since Spring Core 3.1.2), which uses a Spring-managed bean with @Autowired and handles encoding correctly via GORM persistence events.

Changes

usingControllerAnnotations.adoc (tutorial):

  • Added WARNING after the older User domain class pattern explaining that springSecurityService will be null in BootStrap
  • Added new bootstrapPasswordEncoding section with:
    • Explanation of why transient services are null on new instances
    • Complete BootStrap example showing PasswordEncoder injection and pre-encoding
    • TIP about double-encoding risk and recommendation to use the UserPasswordEncoderListener pattern

gormAutowire.adoc (domain classes):

  • Expanded the existing autowiring warning with concrete guidance about freshly constructed instances vs GORM-loaded instances
  • Added cross-reference to the new bootstrapPasswordEncoding section

Copilot AI review requested due to automatic review settings February 17, 2026 16:14
@jamesfredley jamesfredley self-assigned this Feb 17, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 PasswordEncoder injection 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.
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<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.

Copilot uses AI. Check for mistakes.
<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.
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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`).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants