Search code examples
javaspringspring-securityaopaudit

Audit using AspectJ after spring PreAuthorize on controller


I'm trying to audit log both successful and failing OAuth2 authentication attempts in a Spring application. I'm making use of Spring's @PreAuthorize annotation as shown below.

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
@PreAuthorize("#oauth2.hasScope('read')")
public Person getById(@PathVariable String id) {
    return service.getById(id);
}

The authentication and authorization works fine. In an attempt to do audit logging, I tried doing some AOP with AspectJ as shown below.

@Component
@Aspect
public class AuditLogger {


    @After("@annotation(org.springframework.security.access.prepost.PreAuthorize)")
    public void afterAuth(JoinPoint joinPoint) throws Throwable {

        // do audit logging

    }

}

However, this only gets hit when authentication and authorization are both successful. These are the scenarios:

  • Given valid token with read scope - afterAuth is hit
  • Given invalid token - afterAuth not hit
  • Given valid token without read scope - afterAuth not hit

My best guess is that Spring does something differently when authentication or authorization fails, but I'm not sure how to get around it. Any advice?


Solution

  • Couldn't find a good way to do this in one file, so I ended up using one file to handle failed auth requests (using events) and another to handle successful auth requests (using aspects). I annotated all of my controllers with a custom MyAnnotation that contained some information about the controller. This might not be the best solution, but hopefully it helps someone out.

    Failed:

    @Component
    public class FailedAuthRequestAuditor {
    
        private ApplicationContext context;
        private AuditEventLogger logger;
    
        @Autowired
        public FailedAuthRequestAuditor(
            ApplicationContext context,
            AuditEventLogger logger
        ) {
            this.context = context;
            this.logger = logger;
        }
    
        @EventListener
        public void onBadCredentialsEvent(AuthenticationFailureBadCredentialsEvent event) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Method destinationMethod = this.getDestination(request);
    
            if (destinationMethod != null) {
                // do logging here
            }
        }
    
        @EventListener
        public void onAuthorizationFailureEvent(AuthorizationFailureEvent event) {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            Method destinationMethod = this.getDestination(request);
    
            if (destinationMethod != null) {
                // do logging here
            }
        }
    
    
        private Method getDestination(HttpServletRequest request) {
            for (HandlerMapping handlerMapping : context.getBeansOfType(HandlerMapping.class).values()) {
                HandlerExecutionChain handlerExecutionChain = null;
                try {
                    handlerExecutionChain = handlerMapping.getHandler(request);
                } catch (Exception e) {
                    // do nothing
                }
    
                if (handlerExecutionChain != null && handlerExecutionChain.getHandler() instanceof HandlerMethod) {
                    return ((HandlerMethod) handlerExecutionChain.getHandler()).getMethod();
                }
            }
    
            return null;
        }
    
    }
    

    Successful:

    @Aspect
    @Component
    public class SuccessfulAuthRequestAuditor {
    
        private AuditEventLogger auditEventLogger;
        private AuditEventOutcomeMapper mapper;
    
        @Autowired
        public SuccessfulAuthRequestAuditor(AuditEventLogger auditEventLogger, AuditEventOutcomeMapper mapper) {
            this.auditEventLogger = auditEventLogger;
            this.mapper = mapper;
        }
    
        @AfterReturning(pointcut = "execution(@com.company.MyAnnotation * *(..)) && @annotation(myAnnotation) && args(request,..)", returning = "result")
        public void afterReturning(JoinPoint joinPoint, Object result, MyAnnotation myAnnotation, HttpServletRequest request) {
            // do logging
        }
    
        @AfterThrowing(pointcut = "execution(@com.company.MyAnnotation * *(..)) && @annotation(myAnnotation) && args(request,..)", throwing = "exception")
        public void afterThrowing(JoinPoint joinPoint, Throwable exception, MyAnnotation myAnnotation, HttpServletRequest request) {
            // do logging
        }
    }