Simple Audit Log using Grails and Hibernate event listeners
Providing following functionality 1. Loging Insert, Update, Delete of objects 2. Logging Login, Logout events 3. Logging Login Attempt failed event 4. Logging which screen is accessed 1. Create AuditLog domain.
class AuditLog {
static transient escapeLogAction = true
User user
AuditLogType auditLogType // To find which type of action it is. like login, logout, screen access etc
String value
Date timeStamp
String IPAddress
Date dateCreated
Date lastUpdated
AuditLogSource source // To find what is the source of request. like web or system. Some times we run the cron jobs to insert the data. in that case it is "System"
static constraints = {
user (nullable: true)
IPAddress (nullable: true)
source (nullable: true)
}
}
2. Create AuditLogType enum in src/groovy folder
enum AuditLogType {
LOG_IN,
LOG_OUT,
FAILED_ATTEMPT,
SCREEN_ACCESS,
DATA_CHANGE
}
4. Create AuditLogSource enum in src/groovy folder
enum AuditLogSource {
WEB,
SYSTEM
}
3. Create AuditLogListener(src/groovy folder) to log insert/update/delete actions )
import org.hibernate.event.PostDeleteEvent
import org.hibernate.event.PostDeleteEventListener
import org.hibernate.event.PostInsertEvent
import org.hibernate.event.PostInsertEventListener
import org.hibernate.event.PostUpdateEvent
import org.hibernate.event.PostUpdateEventListener
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.RequestContextHolder
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpSession
class AuditLogListener implements PostInsertEventListener, PostUpdateEventListener, PostDeleteEventListener {
def springSecurityService
@Override
void onPostDelete(PostDeleteEvent postDeleteEvent) {
logAction(postDeleteEvent.entity)
}
@Override
void onPostInsert(PostInsertEvent postInsertEvent) {
logAction(postInsertEvent.entity)
}
@Override
void onPostUpdate(PostUpdateEvent postUpdateEvent) {
logAction(postUpdateEvent.entity)
}
private void logAction(Object entity) {
//declaring escapeAuditLog = true in a domain will skip the audit log for that domain.
if (entity.metaClass.hasProperty(entity,'escapeAuditLog') && entity.'escapeAuditLog') {
return
}
HttpServletRequest request = getHttpServletRequest()
HttpSession session = request?.getSession()
AuditLog auditLog = new AuditLog()
auditLog.timeStamp = new Date()
auditLog.actionType = ActionType.DATA_CHANGE
auditLog.user = springSecurityService.currentUser
auditLog.value = entity.getClass().getName()
auditLog.IPAddress = getRemoteAddress(request)
auditLog.source = request ? AuditLogSource.WEB : AuditLogSource.SYSTEM
auditLog.withNewSession {
auditLog.save(flush: true)
}
}
private HttpServletRequest getHttpServletRequest() {
RequestAttributes attribs = RequestContextHolder.getRequestAttributes()
if (attribs instanceof NativeWebRequest) {
HttpServletRequest request = (HttpServletRequest) ((NativeWebRequest) attribs).getNativeRequest()
return request
}
}
private String getRemoteAddress(HttpServletRequest request) {
if (!request) return null
String ipAddress = request.getHeader("X-Forwarded-For") // X-Forwarded-For: clientIpAddress, proxy1, proxy2
if (!ipAddress) {
ipAddress = request.remoteAddr
}
return ipAddress.split(",")[0]
}
}
5. Create LoggingSecurityEventListener(in src/groovy folder) to log the Login, Logout events
import org.apache.log4j.Logger import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationListener import org.springframework.security.authentication.event.AuthenticationSuccessEvent import org.springframework.security.core.Authentication import org.springframework.security.web.authentication.logout.LogoutHandler import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse class LoggingSecurityEventListener implements ApplicationListener6. Create LoginFailedAuthEventListener(in src/groovy folder) to log login failed attempts, LogoutHandler { private static final log = Logger.getLogger(this) def userService @Autowired HttpServletRequest httpServletRequest // Injected and limited to the current thread per usage void onApplicationEvent(AuthenticationSuccessEvent event) { event.authentication.with { def userName = principal.hasProperty('username')?.getProperty(principal) ?: principal log.info("Login success from ${getRemoteAddress(httpServletRequest)} using username $userName") logAction(userName, ActionType.LOG_IN, getRemoteAddress(httpServletRequest)) } } void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { authentication?.with { def username = principal.hasProperty('username')?.getProperty(principal) ?: principal logAction(username, ActionType.LOG_OUT, getRemoteAddress(request)) } } private void logAction(def userName, ActionType actionType, String ipAddress) { AuditLog auditLog = new AuditLog() auditLog.source = ActionLogSource.WEB auditLog.actionType = actionType auditLog.value = userName auditLog.timeStamp = new Date() auditLog.user = User.findByUserName(userName) // modify to get the logged in user auditLog.IPAddress = ipAddress auditLog.save(flush: true) } private String getRemoteAddress(HttpServletRequest request) { if (!request) return null String ipAddress = request.getHeader("X-Forwarded-For") // X-Forwarded-For: clientIpAddress, proxy1, proxy2 if (!ipAddress) { ipAddress = request.remoteAddr } return ipAddress.split(",")[0] } }
import org.apache.log4j.Level import org.apache.log4j.Logger import org.springframework.context.ApplicationListener import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent class LoginFailedAuthEventListener implements ApplicationListener7. Register all these listners in resources.groovy{ private static final log = Logger.getLogger(this) def actionLogService void onApplicationEvent(AbstractAuthenticationFailureEvent event) { log.setLevel(Level.WARN) def userName = event.authentication.principal log.warn("Failed login attempt from $event.source.details.remoteAddress using username $userName") AuditLog auditLog = new AuditLog() auditLog.source = ActionLogSource.WEB auditLog.actionType = ActionType.FAILED_ATTEMPT auditLog.value = userName auditLog.timeStamp = new Date() auditLog.IPAddress = event.source.details.remoteAddress auditLog.save(flush: true) } }
import AuditLogListener
import LoggingSecurityEventListener
import LoginFailedAuthEventListener
import org.codehaus.groovy.grails.orm.hibernate.HibernateEventListeners
// Place your Spring DSL code here
beans = {
loginFailedAuthEventListener(LoginFailedAuthEventListener)
securityEventListener(LoggingSecurityEventListener)
auditLogListner(AuditLogListener) {
springSecurityService = ref("springSecurityService")
}
hibernateEventListeners(HibernateEventListeners) {
listenerMap = ['post-insert':auditLogListner,
'post-update':auditLogListner,
'post-delete':auditLogListner]
}
}
8. Create BaseController(in controllers folder) to log Screen Access and extend this controller from every controller
class BaseController {
def springSecurityService
def beforeInterceptor = {
if (!request.xhr) {
AuditLog auditLog = new AuditLog()
auditLog.source = ActionLogSource.WEB
auditLog.timeStamp = new Date()
auditLog.actionType = ActionType.SCREEN_ACCESS
auditLog.user = User.get(springSecurityService.principal.id)
auditLog.IPAddress = getRemoteAddress(request)
auditLog.value = actionUri
auditLog.save(flush: true)
}
return true
}
private String getRemoteAddress(HttpServletRequest request) {
if (!request) return null
String ipAddress = request.getHeader("X-Forwarded-For") // X-Forwarded-For: clientIpAddress, proxy1, proxy2
if (!ipAddress) {
ipAddress = request.remoteAddr
}
return ipAddress.split(",")[0]
}
}
Categories:
Groovy & Grails
