From a0c696b17b8729b894e318cd9cdef38ecf167be1 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Thu, 5 Nov 2015 21:17:55 +0100 Subject: [PATCH] Allow security AuditListener overrides Introduce `AbstractAuthenticationAuditListener` and `AbstractAuthorizationAuditListener` classes so that users can extended them to replace the auto-configured defaults. Closes gh-4406 --- .../autoconfigure/AuditAutoConfiguration.java | 5 ++ .../AbstractAuthenticationAuditListener.java | 35 ++++++++ .../AbstractAuthorizationAuditListener.java | 35 ++++++++ .../security/AuthenticationAuditListener.java | 10 +-- .../security/AuthorizationAuditListener.java | 10 +-- .../AuditAutoConfigurationTests.java | 88 +++++++++++++++++-- .../AuthenticationAuditListenerTests.java | 2 +- .../AuthorizationAuditListenerTests.java | 2 +- .../asciidoc/production-ready-features.adoc | 4 +- 9 files changed, 165 insertions(+), 26 deletions(-) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthenticationAuditListener.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthorizationAuditListener.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfiguration.java index 9610411a46..3fae0dc3ef 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfiguration.java @@ -21,6 +21,8 @@ import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; import org.springframework.boot.actuate.audit.listener.AuditListener; +import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; +import org.springframework.boot.actuate.security.AbstractAuthorizationAuditListener; import org.springframework.boot.actuate.security.AuthenticationAuditListener; import org.springframework.boot.actuate.security.AuthorizationAuditListener; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -33,6 +35,7 @@ import org.springframework.context.annotation.Configuration; * {@link EnableAutoConfiguration Auto-configuration} for {@link AuditEvent}s. * * @author Dave Syer + * @author Vedran Pavic */ @Configuration public class AuditAutoConfiguration { @@ -47,12 +50,14 @@ public class AuditAutoConfiguration { @Bean @ConditionalOnClass(name = "org.springframework.security.authentication.event.AbstractAuthenticationEvent") + @ConditionalOnMissingBean(AbstractAuthenticationAuditListener.class) public AuthenticationAuditListener authenticationAuditListener() throws Exception { return new AuthenticationAuditListener(); } @Bean @ConditionalOnClass(name = "org.springframework.security.access.event.AbstractAuthorizationEvent") + @ConditionalOnMissingBean(AbstractAuthorizationAuditListener.class) public AuthorizationAuditListener authorizationAuditListener() throws Exception { return new AuthorizationAuditListener(); } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthenticationAuditListener.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthenticationAuditListener.java new file mode 100644 index 0000000000..b011058fab --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthenticationAuditListener.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.security; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; +import org.springframework.security.authentication.event.AbstractAuthenticationEvent; + +/** + * Abstract {@link ApplicationListener} to expose Spring Security + * {@link AbstractAuthenticationEvent authentication events} as {@link AuditEvent}s. + * + * @author Dave Syer + * @author Vedran Pavic + * @since 1.3.0 + */ +public abstract class AbstractAuthenticationAuditListener implements + ApplicationListener, ApplicationEventPublisherAware { + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthorizationAuditListener.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthorizationAuditListener.java new file mode 100644 index 0000000000..14057e4b5d --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AbstractAuthorizationAuditListener.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.security; + +import org.springframework.boot.actuate.audit.AuditEvent; +import org.springframework.context.ApplicationEventPublisherAware; +import org.springframework.context.ApplicationListener; +import org.springframework.security.access.event.AbstractAuthorizationEvent; + +/** + * Abstract {@link ApplicationListener} to expose Spring Security + * {@link AbstractAuthorizationEvent authorization events} as {@link AuditEvent}s. + * + * @author Dave Syer + * @author Vedran Pavic + * @since 1.3.0 + */ +public abstract class AbstractAuthorizationAuditListener implements + ApplicationListener, ApplicationEventPublisherAware { + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java index 5af8bb8d00..7f74cb16ef 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthenticationAuditListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,6 @@ import java.util.Map; import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; @@ -31,13 +29,11 @@ import org.springframework.security.web.authentication.switchuser.Authentication import org.springframework.util.ClassUtils; /** - * {@link ApplicationListener} expose Spring Security {@link AbstractAuthenticationEvent - * authentication events} as {@link AuditEvent}s. + * Default implementation of {@link AuthenticationAuditListener}. * * @author Dave Syer */ -public class AuthenticationAuditListener implements - ApplicationListener, ApplicationEventPublisherAware { +public class AuthenticationAuditListener extends AbstractAuthenticationAuditListener { private static final String WEB_LISTENER_CHECK_CLASS = "org.springframework.security.web.authentication.switchuser.AuthenticationSwitchUserEvent"; diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthorizationAuditListener.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthorizationAuditListener.java index b441727ae6..2d672e0294 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthorizationAuditListener.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/security/AuthorizationAuditListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,20 +22,16 @@ import java.util.Map; import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; import org.springframework.security.access.event.AbstractAuthorizationEvent; import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent; import org.springframework.security.access.event.AuthorizationFailureEvent; /** - * {@link ApplicationListener} expose Spring Security {@link AbstractAuthorizationEvent - * authorization events} as {@link AuditEvent}s. + * Default implementation of {@link AuthorizationAuditListener}. * * @author Dave Syer */ -public class AuthorizationAuditListener implements - ApplicationListener, ApplicationEventPublisherAware { +public class AuthorizationAuditListener extends AbstractAuthorizationAuditListener { private ApplicationEventPublisher publisher; diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfigurationTests.java index c82518ebe4..4bae931068 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/AuditAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,11 +20,16 @@ import org.junit.Test; import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.InMemoryAuditEventRepository; +import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; +import org.springframework.boot.actuate.security.AbstractAuthorizationAuditListener; import org.springframework.boot.actuate.security.AuthenticationAuditListener; import org.springframework.boot.actuate.security.AuthorizationAuditListener; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.event.AbstractAuthorizationEvent; +import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertNotNull; @@ -34,16 +39,15 @@ import static org.junit.Assert.assertThat; * Tests for {@link AuditAutoConfiguration}. * * @author Dave Syer + * @author Vedran Pavic */ public class AuditAutoConfigurationTests { - private AnnotationConfigApplicationContext context; + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @Test public void testTraceConfiguration() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(AuditAutoConfiguration.class); - this.context.refresh(); + registerAndRefresh(AuditAutoConfiguration.class); assertNotNull(this.context.getBean(AuditEventRepository.class)); assertNotNull(this.context.getBean(AuthenticationAuditListener.class)); assertNotNull(this.context.getBean(AuthorizationAuditListener.class)); @@ -51,15 +55,35 @@ public class AuditAutoConfigurationTests { @Test public void ownAutoRepository() throws Exception { - this.context = new AnnotationConfigApplicationContext(); - this.context.register(Config.class, AuditAutoConfiguration.class); - this.context.refresh(); + registerAndRefresh(CustomAuditEventRepositoryConfiguration.class, + AuditAutoConfiguration.class); assertThat(this.context.getBean(AuditEventRepository.class), instanceOf(TestAuditEventRepository.class)); } + @Test + public void ownAuthenticationAuditListener() throws Exception { + registerAndRefresh(CustomAuthenticationAuditListenerConfiguration.class, + AuditAutoConfiguration.class); + assertThat(this.context.getBean(AbstractAuthenticationAuditListener.class), + instanceOf(TestAuthenticationAuditListener.class)); + } + + @Test + public void ownAuthorizationAuditListener() throws Exception { + registerAndRefresh(CustomAuthorizationAuditListenerConfiguration.class, + AuditAutoConfiguration.class); + assertThat(this.context.getBean(AbstractAuthorizationAuditListener.class), + instanceOf(TestAuthorizationAuditListener.class)); + } + + private void registerAndRefresh(Class... annotatedClasses) { + this.context.register(annotatedClasses); + this.context.refresh(); + } + @Configuration - public static class Config { + public static class CustomAuditEventRepositoryConfiguration { @Bean public TestAuditEventRepository testAuditEventRepository() { @@ -71,4 +95,50 @@ public class AuditAutoConfigurationTests { public static class TestAuditEventRepository extends InMemoryAuditEventRepository { } + @Configuration + protected static class CustomAuthenticationAuditListenerConfiguration { + + @Bean + public TestAuthenticationAuditListener authenticationAuditListener() { + return new TestAuthenticationAuditListener(); + } + + } + + protected static class TestAuthenticationAuditListener + extends AbstractAuthenticationAuditListener { + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + } + + @Override + public void onApplicationEvent(AbstractAuthenticationEvent event) { + } + + } + + @Configuration + protected static class CustomAuthorizationAuditListenerConfiguration { + + @Bean + public TestAuthorizationAuditListener authorizationAuditListener() { + return new TestAuthorizationAuditListener(); + } + + } + + protected static class TestAuthorizationAuditListener + extends AbstractAuthorizationAuditListener { + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + } + + @Override + public void onApplicationEvent(AbstractAuthorizationEvent event) { + } + + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java index ddc17175f6..aca33b1803 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthenticationAuditListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java index a94a6670b2..222e3b37cd 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/security/AuthorizationAuditListenerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 2e9930b6dd..945c2306c2 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1324,7 +1324,9 @@ channel and any subscribers you need). Spring Boot Actuator has a flexible audit framework that will publish events once Spring Security is in play ('`authentication success`', '`failure`' and '`access denied`' exceptions by default). This can be very useful for reporting, and also to implement a -lock-out policy based on authentication failures. +lock-out policy based on authentication failures. To customize published security events +you can provide your own implementations of `AbstractAuthenticationAuditListener` and +`AbstractAuthorizationAuditListener`. You can also choose to use the audit services for your own business events. To do that you can either inject the existing `AuditEventRepository` into your own components and