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