Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion plugin-core/docs/src/docs/domainClasses/gormAutowire.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ specific language governing permissions and limitations
under the License.
////

WARNING: Service injection in GORM entities is disabled by default since Grails 3.2.8. Read documentation about https://docs.grails.org/latest/ref/Domain%20Classes/Usage.html#_spring_autowiring_of_domain_instances[Spring Autowiring of Domain Instances] to learn how to turn autowire on.
WARNING: Service injection in GORM entities is disabled by default since Grails 3.2.8. Read documentation about https://docs.grails.org/latest/ref/Domain%20Classes/Usage.html#_spring_autowiring_of_domain_instances[Spring Autowiring of Domain Instances] to learn how to turn autowire on.

Even with autowiring enabled, transient service references (such as `springSecurityService`) are only injected into domain instances that are loaded by GORM from the database. Freshly constructed instances (e.g., `new User(...)`) are plain Groovy objects and will have `null` for all transient service fields. This is particularly important in `BootStrap.groovy`, where newly created domain instances will not have `springSecurityService` available during `beforeInsert()`. See <<bootstrapPasswordEncoding>> for the recommended approach to password encoding when seeding data.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:[&lt;==&gt;] 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 so that password encoding is handled explicitly here, rather than relying on a `beforeInsert()` hook or an injected `springSecurityService` in the domain class.

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`. The recommended approach is to use the `UserPasswordEncoderListener` pattern shown above, which avoids this issue entirely.

==== 6. Start the server.

Expand Down
Loading