1. Overview
In this short article, we’ll explore the Spring Boot Actuator module and the support for publishing authentication and authorization events in conjunction with Spring Security.
2. Maven Dependencies
First, we need to add the spring-boot-starter-actuator to our pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>1.5.2.RELEASE</version> </dependency>
The latest version is available in the Maven Central repository.
3. Listening for Authentication and Authorization Events
To log all authentication and authorization attempts in a Spring Boot application, we can just define a bean with a listener method:
@Component public class LoginAttemptsLogger { @EventListener public void auditEventHappened( AuditApplicationEvent auditApplicationEvent) { AuditEvent auditEvent = auditApplicationEvent.getAuditEvent(); System.out.println("Principal " + auditEvent.getPrincipal() + " - " + auditEvent.getType()); WebAuthenticationDetails details = (WebAuthenticationDetails) auditEvent.getData().get("details"); System.out.println("Remote IP address: " + details.getRemoteAddress()); System.out.println(" Session Id: " + details.getSessionId()); } }
Note that we’re just outputting some of the things that are available in AuditApplicationEvent to show what information is available. In an actual application, you might want to store that information in a repository or cache to process it further.
Note that any Spring bean will work; the basics of the new Spring event support are quite simple:
- annotate the method with @EventListener
- add the AuditApplicationEvent as the sole argument of the method
The output of running the application will look something like to this:
Principal anonymousUser - AUTHORIZATION_FAILURE Remote IP address: 0:0:0:0:0:0:0:1 Session Id: null Principal user - AUTHENTICATION_FAILURE Remote IP address: 0:0:0:0:0:0:0:1 Session Id: BD41692232875A5A65C5E35E63D784F6 Principal user - AUTHENTICATION_SUCCESS Remote IP address: 0:0:0:0:0:0:0:1 Session Id: BD41692232875A5A65C5E35E63D784F6
In this example, three AuditApplicationEvents have been received by the listener:
- Without logging on, access has been requested to a restricted page
- A wrong password has been used while logging on
- A correct password has been used the second time around
4. An Authentication Audit Listener
If the information exposed by Spring Boot’s AuthorizationAuditListener is not enough, you can create your own bean to expose more information.
Let’s have a look at an example, where we also expose the request URL that was accessed when the authorization fails:
@Component public class ExposeAttemptedPathAuthorizationAuditListener extends AbstractAuthorizationAuditListener { public static final String AUTHORIZATION_FAILURE = "AUTHORIZATION_FAILURE"; @Override public void onApplicationEvent(AbstractAuthorizationEvent event) { if (event instanceof AuthorizationFailureEvent) { onAuthorizationFailureEvent((AuthorizationFailureEvent) event); } } private void onAuthorizationFailureEvent( AuthorizationFailureEvent event) { Map<String, Object> data = new HashMap<>(); data.put( "type", event.getAccessDeniedException().getClass().getName()); data.put("message", event.getAccessDeniedException().getMessage()); data.put( "requestUrl", ((FilterInvocation)event.getSource()).getRequestUrl() ); if (event.getAuthentication().getDetails() != null) { data.put("details", event.getAuthentication().getDetails()); } publish(new AuditEvent(event.getAuthentication().getName(), AUTHORIZATION_FAILURE, data)); } }
We can now log the request URL in our listener:
@Component public class LoginAttemptsLogger { @EventListener public void auditEventHappened( AuditApplicationEvent auditApplicationEvent) { AuditEvent auditEvent = auditApplicationEvent.getAuditEvent(); System.out.println("Principal " + auditEvent.getPrincipal() + " - " + auditEvent.getType()); WebAuthenticationDetails details = (WebAuthenticationDetails) auditEvent.getData().get("details"); System.out.println(" Remote IP address: " + details.getRemoteAddress()); System.out.println(" Session Id: " + details.getSessionId()); System.out.println(" Request URL: " + auditEvent.getData().get("requestUrl")); } }
As a result, the output now contains the requested URL:
Principal anonymousUser - AUTHORIZATION_FAILURE Remote IP address: 0:0:0:0:0:0:0:1 Session Id: null Request URL: /hello
Note that we extended from the abstract AbstractAuthorizationAuditListener in this example, so we can use the publish method from that base class in our implementation.
If you want to test it, check out the source code and run:
mvn clean spring-boot:run
Thereafter you can point your browser to http://localhost:8080/.
5. Storing Audit Events
By default, Spring Boot stores the audit events in an AuditEventRepository. If you don’t create a bean with an own implementation, then an InMemoryAuditEventRepository will be wired for you.
The InMemoryAuditEventRepository is a kind of circular buffer that stores the last 4000 audit events in memory. Those events can then be accessed via the management endpoint http://localhost:8080/auditevents.
This returns a JSON representation of the audit events:
{ "events": [ { "timestamp": "2017-03-09T19:21:59+0000", "principal": "anonymousUser", "type": "AUTHORIZATION_FAILURE", "data": { "requestUrl": "/auditevents", "details": { "remoteAddress": "0:0:0:0:0:0:0:1", "sessionId": null }, "type": "org.springframework.security.access.AccessDeniedException", "message": "Access is denied" } }, { "timestamp": "2017-03-09T19:22:00+0000", "principal": "anonymousUser", "type": "AUTHORIZATION_FAILURE", "data": { "requestUrl": "/favicon.ico", "details": { "remoteAddress": "0:0:0:0:0:0:0:1", "sessionId": "18FA15865F80760521BBB736D3036901" }, "type": "org.springframework.security.access.AccessDeniedException", "message": "Access is denied" } }, { "timestamp": "2017-03-09T19:22:03+0000", "principal": "user", "type": "AUTHENTICATION_SUCCESS", "data": { "details": { "remoteAddress": "0:0:0:0:0:0:0:1", "sessionId": "18FA15865F80760521BBB736D3036901" } } } ] }
6. Conclusion
With the actuator support in Spring Boot, it becomes trivial to log the authentication and authorization attempts from users. The reader is also referred to production ready auditing for some additional information.
The code from this article can be found over on GitHub