日期:2014-05-16  浏览次数:20645 次

以入侵的方式实现基于Grails+db4o的数据稽查(Audit Trail)

要做Audit Trail,即跟踪记录对数据的所有CRUD操作,实现方式大体分两种类型:

1. Brute Force型,即在代码中写大量的类似于这样的代码

new OpLog(
    // properties ...
).save()

2. 技术技巧型,即使用优雅的事件监听机制。

?

由于使用grails/groovy和db4o,实现Audit Trail显得尤为简洁。

?

首先,写一个简单得不能再简单的打入db4o内部的(不在乎侵入不侵入了,反正已经吊死在grails和db4o上了,反正大家都已经紧密地结合了,反正谁都离不开谁了)持久化事件引擎:

package com.db4o.internal

public class InvasivePersistentEventEngine {
	
	private static HANDLERS = [:]
	static on(evt, handler) {
	    if(HANDLERS[evt] == null) {
	        HANDLERS[evt] = new HashSet()
	    }
	    HANDLERS[evt] << handler
	}
	static fireEvent(evt, obj) {
	    if(!HANDLERS[evt]) {
	        return
	    }
	    for(h in HANDLERS[evt]) {
	        h(obj)
	    }
	}
}
?

然后,稍稍修改一下db4o的两个类(从代码可以看出,是受到db4o的Log Message的启发,只要在configure一下messageLevel,把它设置为自己指定的Integer.MIN_VALUE就可以了)

public abstract class ObjectContainerBase  implements TransientClass, Internal4, ObjectContainerSpec, InternalObjectContainer {
    //...	
    public final int store3(Transaction trans, Object obj, int updateDepth, boolean checkJustSet) {
        //。。。
        if (configImpl().messageLevel() > Const4.STATE) {
            message("" + ref.getID() + " new " + ref.classMetadata().getName());
        }
        //+S.C.
        else if (configImpl().messageLevel() == Integer.MIN_VALUE) {
            InvasivePersistentEventEngine.fireEvent("new", ref.getObject());
        }
	//。。。
    }
}

?

?

public class ObjectReference extends PersistentBase implements ObjectInfo, Activator {
    //...
    private void logEvent(ObjectContainerBase container, String event, final int level) {
        if (container.configImpl().messageLevel() > level) {
            container.message("" + getID() + " " + event + " " + _class.getName());
        }
        //+S.C.
        else if (container.configImpl().messageLevel() == Integer.MIN_VALUE) {
            if(event == "update") {
                InvasivePersistentEventEngine.fireEvent(event, getObject());     
            }
        }
    }
}
?

小小地测试一下,先注入handlers。在Web Console中执行

import com.db4o.internal.InvasivePersistentEventEngine
import framework.utils.GroovyDataUtils
InvasivePersistentEventEngine.on('new', {o->
    println "object created [${o.id}, ${o.version}]: " + GroovyDataUtils.getProperties(o)
})
InvasivePersistentEventEngine.on('update', {o->
    println "object ${o.deleted?'deleted':'updated'} [${o.id}, ${o.version}]: " + GroovyDataUtils.getProperties(o)
})
?

然后执行

new User(username:'t@t.cc', password:'**').save()

输出object created [16eb7e6c-8453-4e3d-8ddb-281470ed64f7, 0]: [username:t@t.cc, locked:null, password:**, profile:null, profileId:null]

继续执行

def user = User.find(username:'t@t.cc')
user.password = 'password_changed'
user.save()

输出object updated [16eb7e6c-8453-4e3d-8ddb-281470ed64f7, 1]: [password:password_changed, username:t@t.cc, locked:null, profile:null, profileId:null]

object created [16eb7e6c-8453-4e3d-8ddb-281470ed64f7_bak_0, 0]: [username:t@t.cc, locked:null, password:**, profile:null, profileId:null]

最后执行删除看一下

def user = User.find(username:'t@t.cc')
user.delete()

输出object deleted [16eb7e6c-8453-4e3d-8ddb-281470ed64f7, 1]: [password:password_changed, username:t@t.cc, locked:null, profile:null, profileId:null]

?

入侵成功。