I'm using Grails 3.3.2 with the mongoDB plugin (v6.1.4) and Spring Security Core plugin (v3.2.0).
I have the following UserPasswordEncoderListener
with the following persistenceEvent method:
@Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
if (event.entityObject instanceof User) {
User u = (event.entityObject as User)
if (u.password && (event.eventType == EventType.PreInsert || (event.eventType == EventType.PreUpdate && u.hasChanged('password')))) {
event.getEntityAccess().setProperty("password", encodePassword(u.password))
}
}
}
The problem is the hasChanged
call always return true every time I save a user object that has NO updates causing an already encoded password to be re-encoded and thereby breaking authentication.
One workaround will be to do it the old way and just retrieve the original password from the db and compare them before encoding but I'm wondering why hasChanged
is falsely returning true.
I tested that hasChanged
behaves correctly elsewhere by running the following in the groovy console:
def user = User.findByEmail("[email protected]")
println "Result: "+ user.hasChanged('password')
The result is Result: false
.
Why does it NOT work in the persistence listener class?
FYI: I have the following bean defined in resources.groovy:
userPasswordEncoderListener(UserPasswordEncoderListener,ref('mongoDatastore'))
Have you tried using isDirty()
rather than hasChanged()
in your listener?
For example:
package com.mycompany.myapp
import grails.plugin.springsecurity.SpringSecurityService
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.PreInsertEvent
import org.grails.datastore.mapping.engine.event.PreUpdateEvent
import org.springframework.beans.factory.annotation.Autowired
import grails.events.annotation.gorm.Listener
import groovy.transform.CompileStatic
@CompileStatic
class UserPasswordEncoderListener {
@Autowired
SpringSecurityService springSecurityService
@Listener(User)
void onPreInsertEvent(PreInsertEvent event) {
encodePasswordForEvent(event)
}
@Listener(User)
void onPreUpdateEvent(PreUpdateEvent event) {
encodePasswordForEvent(event)
}
private void encodePasswordForEvent(AbstractPersistenceEvent event) {
if (event.entityObject instanceof User) {
User u = event.entityObject as User
if (u.password && ((event instanceof PreInsertEvent) || (event instanceof PreUpdateEvent && u.isDirty('password')))) {
event.getEntityAccess().setProperty('password', encodePassword(u.password))
}
}
}
private String encodePassword(String password) {
springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
}
More info is available at: https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#tutorials