In many applications it’s important to react to failed logins and other security-critical events, for example to log the failed login attempt to a file or to display a captcha after repeated failures. Spring-based applications come with a simple mechanism to access this kind of information using Application Events.
Spring’s Application Events are a variation of the old Observer pattern where an object can subscribe to an Observable to receive notifications. With Application Events, you define a @Component
that either implements ApplicationListener or has an @EventListener
method that gets the event as a parameter (see below).
There are two event sources available. The first is Spring Security itself which publishes fine-grained events on authentication and authorization. The second one is Spring Boot’s Actuator which listens to Spring Security’s events and maps them to AuditApplicationEvent instances where each contains an AuditEvent. This is part of Spring Boot’s Auditing features.
Listening to AuditApplicationEvent
instances is very simple:
@Component public class MyAuditListener { private static final Logger LOG = LoggerFactory.getLogger("security"); @EventListener public void onAuditEvent(AuditApplicationEvent event) { AuditEvent auditEvent = event.getAuditEvent(); LOG.info("type={}, principal={}", auditEvent.getType(), auditEvent.getPrincipal()); } }
The AuditEvent has a type
property where the following values are relevant for security logging:
AUTHENTICATION_SUCCESS
(successfully logged in; triggered on each request for stateless applications)AUTHENTICATION_FAILURE
(bad credentials etc.)AUTHENTICATION_SWITCH
(for user impersonation)AUTHORIZATION_FAILURE
(insufficient permissions to access a resource)
The principal
property typically contains the user name and there’s a data
property for more details.
Usually, I would use a switch statement on the type property, but if you prefer, you can use Spring’s Expression Language to further restrict the listener method:
@EventListener(condition = "#event.auditEvent.type == 'AUTHENTICATION_FAILURE'") public void onAuthFailure(AuditApplicationEvent event) { AuditEvent auditEvent = event.getAuditEvent(); LOG.info("authentication failure, user: {}", auditEvent.getPrincipal()); }
In your last code snippet the method name is onAuthSuccess but it’s handling failed loggings
Fixed – thank you! Spring doesn’t care about the method name, but it’s very misleading :)
APPLICATION FAILED TO START on Latest Spring Boot.
Error starting ApplicationContext. To display the conditions report re-run your application with ‘debug’ enabled.
2020-04-09 09:18:24.326 ERROR 30573 — [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in pl.codeleak.demos.sbt.security.AuditEventListener required a bean of type ‘org.springframework.boot.actuate.audit.AuditEventRepository’ that could not be found.
Action:
Consider defining a bean of type ‘org.springframework.boot.actuate.audit.AuditEventRepository’ in your configuration.
https://github.com/hendisantika/springboot-thymeleaf-sample
Any suggest?
Why are you trying to inject that repository? If it’s just about logging audit events you don’t need it. Remove the repo from your code and and it’ll work.
Which file I should remove?
Or Maybe You can try to run my repo to see the issue.
Thanks
I had a quick look. Go to your AuditEventListener class and remove the constructor, the auditEventRepository attribute, and the call to the repository.
After removing as your suggest. It’s working fine now. Unless I can’t login.
BTW, If We remove the AuditEventRepository We can’t save it the event. Is that OK?
Thanks
I don’t believe you have to save events to the repository yourself, the framework should do it for you. Check the /actuator/auditevents endpoint, I think that displays the contents of the repository.
I already update my repo. But, I can’t see the Audit Event log in my console. Can You run my repo on your local?
Thanks
You probably have to expose the actuator endpoints. Check the documentation, look for the management.endpoints.web.exposure.include property.