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:
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?
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
}
}