Creating Simple Audit Log using grails, Spring Security Listeners, Hibernate Listeners



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 ApplicationListener, 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]
    }
}
6. Create LoginFailedAuthEventListener(in src/groovy folder) to log login failed attempts
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 ApplicationListener {
    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)
    }
}
7. Register all these listners in resources.groovy
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: