-
Notifications
You must be signed in to change notification settings - Fork 229
Document springSecurityService null during BootStrap password encoding #1207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -220,6 +220,8 @@ class User implements Serializable { | |||||
| } | ||||||
| ---- | ||||||
|
|
||||||
| WARNING: If you use this older pattern, be aware that `springSecurityService` will be `null` when creating User instances in `BootStrap.groovy`. Domain class instances are plain Groovy objects, not Spring-managed beans, so their transient service references are not autowired at construction time. The `beforeInsert()` callback will silently skip password encoding because of the `?.` safe-navigation operator, resulting in plaintext passwords stored in the database. See <<bootstrapPasswordEncoding>> below for the recommended approach to seeding users in BootStrap. | ||||||
|
|
||||||
| include::../domainClasses/gormAutowire.adoc[] | ||||||
|
|
||||||
| `s2-quickstart` script generates this Role too: | ||||||
|
|
@@ -430,6 +432,49 @@ Some things to note about the preceding `BootStrap.groovy`: | |||||
|
|
||||||
| * The example does not use a traditional GORM many-to-many mapping for the User pass:[<==>] Role relationship; instead you are mapping the join table with the `UserRole` class. This performance optimization helps significantly when many users have one or more common roles. | ||||||
| * We explicitly flush (using `withSession`) because `BootStrap` does not run in a transaction or OpenSessionInView. | ||||||
| * This example works correctly because the `UserPasswordEncoderListener` (a Spring-managed bean) handles password encoding during the GORM `PreInsertEvent`. If you are using the older domain class pattern with `beforeInsert()` instead, see the note below. | ||||||
|
|
||||||
| [[bootstrapPasswordEncoding]] | ||||||
| ===== Password Encoding in BootStrap | ||||||
|
|
||||||
| If you are using the older User domain class pattern where `springSecurityService` is declared as a `transient` field and password encoding happens in `beforeInsert()`, passwords will **not** be encoded when creating users in `BootStrap.groovy`. This is because domain class instances are plain Groovy objects - their transient service references are not autowired by Spring at construction time. The `springSecurityService` field will be `null`, and the safe-navigation operator (`?.`) in `encodePassword()` will silently skip encoding. | ||||||
|
|
||||||
| To work around this, inject the `passwordEncoder` bean directly in `BootStrap` and pre-encode passwords before saving: | ||||||
|
|
||||||
| [source,groovy] | ||||||
| .`BootStrap.groovy` | ||||||
| ---- | ||||||
| import grails.gorm.transactions.Transactional | ||||||
| import org.springframework.security.crypto.password.PasswordEncoder | ||||||
|
|
||||||
| class BootStrap { | ||||||
|
|
||||||
| PasswordEncoder passwordEncoder // <1> | ||||||
|
|
||||||
| def init = { | ||||||
| addTestUser() | ||||||
| } | ||||||
|
|
||||||
| @Transactional | ||||||
| void addTestUser() { | ||||||
| def adminRole = new Role(authority: 'ROLE_ADMIN').save() | ||||||
|
|
||||||
| String encodedPassword = passwordEncoder.encode('password') // <2> | ||||||
| def testUser = new User(username: 'me', password: encodedPassword).save() | ||||||
|
|
||||||
| UserRole.create testUser, adminRole | ||||||
|
|
||||||
| UserRole.withSession { | ||||||
| it.flush() | ||||||
| it.clear() | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| ---- | ||||||
| <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. | ||||||
|
||||||
| 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`). |
There was a problem hiding this comment.
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”, butbeforeInsert()will still run; pre-encoding just avoids relying onspringSecurityServicebeing injected to perform encoding. Please reword this to avoid implying the GORM callback is skipped.