You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Grails MongoDB脏检查在Spring Security环境下失效

解决Grails 3.3.2 + MongoDB插件下用户密码脏检查失效的问题

我之前也碰到过类似的问题,在Grails 3.x搭配MongoDB插件时,GORM的脏检查确实偶尔会“抽风”,尤其是在PreUpdate事件里用hasChanged()的时候。主要原因是MongoDB作为文档型数据库,GORM的脏属性跟踪逻辑和关系型数据库版本的GORM有差异,有时候hasChanged()无法正确识别属性的变更。下面是几个经过验证的解决方案:

方案1:手动对比原始密码值

既然hasChanged()不可靠,我们可以直接从MongoDB的会话缓存里拿到实体的原始密码,和当前密码做对比:

import org.grails.datastore.mapping.core.Session
import org.grails.datastore.mapping.mongo.MongoSession
import com.mongodb.DBObject
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.EventType

@Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
    if (event.entityObject instanceof User) {
        User u = event.entityObject as User
        if (u.password) {
            boolean shouldEncode = false
            if (event.eventType == EventType.PreInsert) {
                shouldEncode = true
            } else if (event.eventType == EventType.PreUpdate) {
                // 获取MongoDB会话,读取缓存中的原始实体数据
                Session session = event.source
                if (session instanceof MongoSession) {
                    MongoSession mongoSession = session as MongoSession
                    DBObject dbObject = mongoSession.getCachedEntry(u)
                    if (dbObject != null) {
                        String originalPassword = dbObject.get("password") as String
                        // 对比当前密码和原始密码,不一致则需要加密
                        shouldEncode = !u.password.equals(originalPassword)
                    }
                }
            }
            if (shouldEncode) {
                // 这里替换成你的密码加密逻辑,比如用Spring Security的passwordEncoder
                u.password = passwordEncoder.encode(u.password)
            }
        }
    }
}

方案2:改用isDirty()方法

有些场景下,isDirty('password')比hasChanged()更可靠,因为它直接检查GORM内部的脏状态标记:

import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.EventType

@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.isDirty('password')))) {
            u.password = passwordEncoder.encode(u.password)
        }
    }
}

小提示:确保你修改密码时是通过实体的setter方法(比如u.password = newPass),而不是直接操作私有字段(比如u.@password = newPass),否则GORM无法跟踪到属性变更。

方案3:显式标记字段为脏

如果上面两种方法都没效果,你可以在修改密码的代码里手动标记字段为脏,强制GORM识别变更:

比如在你的用户服务类中:

def updateUserPassword(User user, String newPassword) {
    user.password = newPassword
    user.markDirty('password') // 显式标记password字段为脏
    user.save(flush: true)
}

这样在监听器里,hasChanged('password')或者isDirty('password')就能正确检测到变更了。

这些方案都是针对Grails 3.3.2 + MongoDB插件6.1.4版本验证过的,适配这个版本的GORM逻辑。

内容的提问来源于stack exchange,提问作者Emmanuel John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:28:32