diff --git a/spring-bootstrap-service/README.md b/spring-bootstrap-service/README.md index 69f43244d6..880d48f259 100644 --- a/spring-bootstrap-service/README.md +++ b/spring-bootstrap-service/README.md @@ -22,8 +22,9 @@ production, and in other environments. |Logging |Logback, Log4j or JDK | Whatever is on the classpath. Sensible defaults. | |Database |HSQLDB or H2 | Per classpath, or define a DataSource to override | |Externalized configuration | Properties or YAML | Support for Spring profiles. Bind automatically to @Bean. | +|Audit | Spring Security and Spring ApplicationEvent |Flexible abstraction with sensible defaults for security events | |Validation | JSR-303 | | -|Management endpoints | Spring MVC | Health, basic metrics, request tracing, shutdown | +|Management endpoints | Spring MVC | Health, basic metrics, request tracing, shutdown, thread dumps | |Error pages | Spring MVC | Sensible defaults based on exception and status code | |JSON |Jackson 2 | | |ORM |Spring Data JPA | If on the classpath | @@ -33,7 +34,7 @@ production, and in other environments. # Getting Started You will need Java (6 at least) and a build tool (Maven is what we use -below, but you are more than wecome to use gradle). These can be +below, but you are more than welcome to use gradle). These can be downloaded or installed easily in most operating systems. FIXME: short instructions for Mac and Linux. @@ -101,12 +102,17 @@ Then in another terminal you if the application is running and healthy. `/varz` is the default location for the metrics endpoint - it gives you basic counts and response timing data by default but there are plenty of ways to -customize it. +customize it. You can also try `/trace` and `/dump` to get some +interesting information about how and what your app is doing. + +What about the home page? $ curl localhost:8080/ {"status": 404, "error": "Not Found", "message": "Not Found"} -That's OK, we haven't added any business content yet. +That's OK, we haven't added any business content yet. But it shows +that there are sensible defaults built in for rendering HTTP and +server-side errors. ## Adding a business endpoint @@ -146,7 +152,48 @@ and re-package: $ curl localhost:8080/ {"message": "Hello World"} -# Add a database +# Adding security + +If you add Spring Security java config to your runtime classpath you +will enable HTTP basic authentication by default on all the endpoints. +In the `pom.xml` it would look like this: + + + org.springframework.security + spring-security-javaconfig + 1.0.0.BUILD-SNAPSHOT + + +(Spring Security java config is still work in progress so we have used +a snapshot. Beware of sudden changes. FIXME: update to full +release.) + +Try it out: + + $ curl localhost:8080/ + {"status": 403, "error": "Forbidden", "message": "Access Denied"} + $ curl user:password@localhost:8080/ + {"message": "Hello World"} + +The default auto configuration has an in-memory user database with one +entry. If you want to extend or expand that, or point to a database +or directory server, you only need to provide a `@Bean` definition for +an `AuthenticationManager`, e.g. in your `SampleController`: + + @Bean + public AuthenticationManager authenticationManager() throws Exception { + return new AuthenticationBuilder().inMemoryAuthentication().withUser("client") + .password("secret").roles("USER").and().and().build(); + } + +Try it out: + + $ curl user:password@localhost:8080/ + {"status": 403, "error": "Forbidden", "message": "Access Denied"} + $ curl client:secret@localhost:8080/ + {"message": "Hello World"} + +# Adding a database Just add `spring-jdbc` and an embedded database to your dependencies: diff --git a/spring-bootstrap-service/pom.xml b/spring-bootstrap-service/pom.xml index b291904291..1efca15fc0 100644 --- a/spring-bootstrap-service/pom.xml +++ b/spring-bootstrap-service/pom.xml @@ -40,11 +40,6 @@ javax.servlet-api provided - - joda-time - joda-time - 1.6 - org.hibernate hibernate-validator @@ -65,10 +60,6 @@ com.fasterxml.jackson.core jackson-databind - - com.fasterxml.jackson.datatype - jackson-datatype-joda - org.springframework.security spring-security-javaconfig diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/AuditConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/AuditConfiguration.java new file mode 100644 index 0000000000..51ab15009e --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/AuditConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2013 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.bootstrap.autoconfigure.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; +import org.springframework.bootstrap.service.audit.AuditEventRepository; +import org.springframework.bootstrap.service.audit.InMemoryAuditEventRepository; +import org.springframework.bootstrap.service.audit.listener.AuditListener; +import org.springframework.bootstrap.service.security.AuthenticationAuditListener; +import org.springframework.bootstrap.service.security.AuthorizationAuditListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Dave Syer + * + */ +@Configuration +public class AuditConfiguration { + + @Autowired(required = false) + private AuditEventRepository auditEventRepository = new InMemoryAuditEventRepository(); + + @Bean + @ConditionalOnMissingBean(AuditEventRepository.class) + public AuditEventRepository auditEventRepository() throws Exception { + return this.auditEventRepository; + } + + @Bean + public AuditListener auditListener() throws Exception { + return new AuditListener(this.auditEventRepository); + } + + @Bean + public AuthenticationAuditListener authenticationAuditListener() throws Exception { + return new AuthenticationAuditListener(); + } + + @Bean + public AuthorizationAuditListener authorizationAuditListener() throws Exception { + return new AuthorizationAuditListener(); + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/MetricFilterConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/MetricFilterConfiguration.java index 194fc2f441..9eb451e41a 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/MetricFilterConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/MetricFilterConfiguration.java @@ -27,8 +27,6 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.joda.time.DateTime; -import org.joda.time.Duration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.bootstrap.context.annotation.ConditionalOnBean; import org.springframework.bootstrap.context.annotation.ConditionalOnClass; @@ -81,7 +79,7 @@ public class MetricFilterConfiguration { UrlPathHelper helper = new UrlPathHelper(); String suffix = helper.getPathWithinApplication(servletRequest); int status = 999; - DateTime t0 = new DateTime(); + long t0 = System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { @@ -90,7 +88,7 @@ public class MetricFilterConfiguration { } catch (Exception e) { // ignore } - set("response", suffix, new Duration(t0, new DateTime()).getMillis()); + set("response", suffix, System.currentTimeMillis() - t0); increment("status." + status, suffix); } } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/SecurityConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/SecurityConfiguration.java index 47f6ebedf6..d968630d4c 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/SecurityConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/SecurityConfiguration.java @@ -24,28 +24,39 @@ import org.springframework.bootstrap.context.annotation.EnableConfigurationPrope import org.springframework.bootstrap.service.properties.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.authentication.AuthenticationBuilder; import org.springframework.security.config.annotation.web.EnableWebSecurity; import org.springframework.security.config.annotation.web.ExpressionUrlAuthorizations; import org.springframework.security.config.annotation.web.HttpConfigurator; import org.springframework.security.config.annotation.web.SpringSecurityFilterChainBuilder.IgnoredRequestRegistry; -import org.springframework.security.config.annotation.web.WebSecurityConfiguration; import org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapter; -import org.springframework.stereotype.Component; /** * @author Dave Syer */ @Configuration @ConditionalOnClass({ EnableWebSecurity.class }) -@ConditionalOnMissingBean({ WebSecurityConfiguration.class }) @EnableWebSecurity @EnableConfigurationProperties(SecurityProperties.class) public class SecurityConfiguration { - @Component - public static class WebSecurityAdapter extends WebSecurityConfigurerAdapter { + @Bean + @ConditionalOnMissingBean({ AuthenticationEventPublisher.class }) + public AuthenticationEventPublisher authenticationEventPublisher() { + return new DefaultAuthenticationEventPublisher(); + } + + @Bean + public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() { + return new BoostrapWebSecurityConfigurerAdapter(); + } + + private static class BoostrapWebSecurityConfigurerAdapter extends + WebSecurityConfigurerAdapter { @Value("${endpoints.healthz.path:/healthz}") private String healthzPath = "/healthz"; @@ -53,6 +64,9 @@ public class SecurityConfiguration { @Autowired private SecurityProperties security; + @Autowired + private AuthenticationEventPublisher authenticationEventPublisher; + @Override protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) { ignoredRequests.antMatchers(this.healthzPath); @@ -69,6 +83,17 @@ public class SecurityConfiguration { if (this.security.isRequireSsl()) { http.requiresChannel().antMatchers("/**").requiresSecure(); } + + } + + @Override + protected AuthenticationManager authenticationManager() throws Exception { + AuthenticationManager manager = super.authenticationManager(); + if (manager instanceof ProviderManager) { + ((ProviderManager) manager) + .setAuthenticationEventPublisher(this.authenticationEventPublisher); + } + return manager; } } @@ -84,4 +109,5 @@ public class SecurityConfiguration { } } + } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java index 7b3b922303..48f21de1ca 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.joda.JodaModule; /** * {@link EnableAutoConfiguration Auto-configuration} for service apps. @@ -42,7 +41,7 @@ import com.fasterxml.jackson.datatype.joda.JodaModule; @Configuration @Import({ ManagementConfiguration.class, MetricConfiguration.class, ServerConfiguration.class, SecurityConfiguration.class, - MetricFilterConfiguration.class }) + MetricFilterConfiguration.class, AuditConfiguration.class }) public class ServiceAutoConfiguration extends WebMvcConfigurationSupport { @Override @@ -51,7 +50,6 @@ public class ServiceAutoConfiguration extends WebMvcConfigurationSupport { for (HttpMessageConverter converter : converters) { if (converter instanceof MappingJackson2HttpMessageConverter) { MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter; - jacksonConverter.getObjectMapper().registerModule(new JodaModule()); jacksonConverter.getObjectMapper().disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/TraceConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/TraceConfiguration.java index 82a3390ac1..15e225859e 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/TraceConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/TraceConfiguration.java @@ -24,8 +24,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; +import org.springframework.bootstrap.service.security.SecurityFilterPostProcessor; import org.springframework.bootstrap.service.trace.InMemoryTraceRepository; -import org.springframework.bootstrap.service.trace.SecurityFilterPostProcessor; import org.springframework.bootstrap.service.trace.TraceEndpoint; import org.springframework.bootstrap.service.trace.TraceRepository; import org.springframework.context.annotation.Bean; diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java index c76b689b8a..d3bdad5c37 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/AuditEvent.java @@ -90,4 +90,10 @@ public class AuditEvent { return result; } + @Override + public String toString() { + return "AuditEvent [timestamp=" + this.timestamp + ", principal=" + + this.principal + ", type=" + this.type + ", data=" + this.data + "]"; + } + } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java index 6c1bd1d01f..52e9785dd0 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/audit/listener/AuditListener.java @@ -15,6 +15,8 @@ */ package org.springframework.bootstrap.service.audit.listener; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.bootstrap.service.audit.AuditEventRepository; import org.springframework.context.ApplicationListener; @@ -24,6 +26,8 @@ import org.springframework.context.ApplicationListener; */ public class AuditListener implements ApplicationListener { + private static Log logger = LogFactory.getLog(AuditListener.class); + private final AuditEventRepository auditEventRepository; public AuditListener(AuditEventRepository auditEventRepository) { @@ -32,6 +36,7 @@ public class AuditListener implements ApplicationListener @Override public void onApplicationEvent(AuditApplicationEvent event) { + logger.info(event.getAuditEvent()); this.auditEventRepository.add(event.getAuditEvent()); } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultCounterService.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultCounterService.java index 8f73f646de..18062d95a4 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultCounterService.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultCounterService.java @@ -16,7 +16,7 @@ package org.springframework.bootstrap.service.metrics; -import org.joda.time.DateTime; +import java.util.Date; /** * @author Dave Syer @@ -35,17 +35,17 @@ public class DefaultCounterService implements CounterService { @Override public void increment(String metricName) { - this.counterRepository.increment(wrap(metricName), 1, new DateTime()); + this.counterRepository.increment(wrap(metricName), 1, new Date()); } @Override public void decrement(String metricName) { - this.counterRepository.increment(wrap(metricName), -1, new DateTime()); + this.counterRepository.increment(wrap(metricName), -1, new Date()); } @Override public void reset(String metricName) { - this.counterRepository.set(wrap(metricName), 0, new DateTime()); + this.counterRepository.set(wrap(metricName), 0, new Date()); } private String wrap(String metricName) { diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultGaugeService.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultGaugeService.java index 404d6cd08b..afc57d4d4d 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultGaugeService.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/DefaultGaugeService.java @@ -16,7 +16,7 @@ package org.springframework.bootstrap.service.metrics; -import org.joda.time.DateTime; +import java.util.Date; /** * @author Dave Syer @@ -35,7 +35,7 @@ public class DefaultGaugeService implements GaugeService { @Override public void set(String metricName, double value) { - this.metricRepository.set(wrap(metricName), value, new DateTime()); + this.metricRepository.set(wrap(metricName), value, new Date()); } private String wrap(String metricName) { diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/InMemoryMetricRepository.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/InMemoryMetricRepository.java index a8098973fb..e8dd216cd4 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/InMemoryMetricRepository.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/InMemoryMetricRepository.java @@ -18,11 +18,10 @@ package org.springframework.bootstrap.service.metrics; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.joda.time.DateTime; - /** * @author Dave Syer */ @@ -31,27 +30,27 @@ public class InMemoryMetricRepository implements MetricRepository { private ConcurrentMap metrics = new ConcurrentHashMap(); @Override - public void increment(String metricName, int amount, DateTime dateTime) { + public void increment(String metricName, int amount, Date timestamp) { Measurement current = this.metrics.get(metricName); if (current != null) { Metric metric = current.getMetric(); this.metrics.replace(metricName, current, - new Measurement(dateTime, metric.increment(amount))); + new Measurement(timestamp, metric.increment(amount))); } else { - this.metrics.putIfAbsent(metricName, new Measurement(dateTime, new Metric( + this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric( metricName, amount))); } } @Override - public void set(String metricName, double value, DateTime dateTime) { + public void set(String metricName, double value, Date timestamp) { Measurement current = this.metrics.get(metricName); if (current != null) { Metric metric = current.getMetric(); this.metrics.replace(metricName, current, - new Measurement(dateTime, metric.set(value))); + new Measurement(timestamp, metric.set(value))); } else { - this.metrics.putIfAbsent(metricName, new Measurement(dateTime, new Metric( + this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric( metricName, value))); } } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/Measurement.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/Measurement.java index bb9d602cf0..3a857c75be 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/Measurement.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/Measurement.java @@ -16,24 +16,24 @@ package org.springframework.bootstrap.service.metrics; -import org.joda.time.DateTime; +import java.util.Date; /** * @author Dave Syer */ public class Measurement { - private DateTime dateTime; + private Date timestamp; private Metric metric; - public Measurement(DateTime dateTime, Metric metric) { - this.dateTime = dateTime; + public Measurement(Date timestamp, Metric metric) { + this.timestamp = timestamp; this.metric = metric; } - public DateTime getDateTime() { - return this.dateTime; + public Date getTimestamp() { + return this.timestamp; } public Metric getMetric() { @@ -42,7 +42,7 @@ public class Measurement { @Override public String toString() { - return "Measurement [dateTime=" + this.dateTime + ", metric=" + this.metric + "]"; + return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric + "]"; } @Override @@ -50,7 +50,7 @@ public class Measurement { final int prime = 31; int result = 1; result = prime * result - + ((this.dateTime == null) ? 0 : this.dateTime.hashCode()); + + ((this.timestamp == null) ? 0 : this.timestamp.hashCode()); result = prime * result + ((this.metric == null) ? 0 : this.metric.hashCode()); return result; } @@ -64,10 +64,10 @@ public class Measurement { if (getClass() != obj.getClass()) return false; Measurement other = (Measurement) obj; - if (this.dateTime == null) { - if (other.dateTime != null) + if (this.timestamp == null) { + if (other.timestamp != null) return false; - } else if (!this.dateTime.equals(other.dateTime)) + } else if (!this.timestamp.equals(other.timestamp)) return false; if (this.metric == null) { if (other.metric != null) diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/MetricRepository.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/MetricRepository.java index 34a03df716..d903582f8a 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/MetricRepository.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/metrics/MetricRepository.java @@ -17,17 +17,16 @@ package org.springframework.bootstrap.service.metrics; import java.util.Collection; - -import org.joda.time.DateTime; +import java.util.Date; /** * @author Dave Syer */ public interface MetricRepository { - void increment(String metricName, int amount, DateTime dateTime); + void increment(String metricName, int amount, Date timestamp); - void set(String metricName, double value, DateTime dateTime); + void set(String metricName, double value, Date timestamp); void delete(String metricName); diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthenticationAuditListener.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthenticationAuditListener.java new file mode 100644 index 0000000000..e4b2ced3ee --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthenticationAuditListener.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2013 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.bootstrap.service.security; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.bootstrap.service.audit.AuditEvent; +import org.springframework.bootstrap.service.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.web.authentication.switchuser.AuthenticationSwitchUserEvent; + +/** + * @author Dave Syer + * + */ +public class AuthenticationAuditListener implements + ApplicationListener, ApplicationEventPublisherAware { + + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public void onApplicationEvent(AbstractAuthenticationEvent event) { + Map data = new HashMap(); + if (event instanceof AbstractAuthenticationFailureEvent) { + data.put("type", ((AbstractAuthenticationFailureEvent) event).getException() + .getClass().getName()); + data.put("message", ((AbstractAuthenticationFailureEvent) event) + .getException().getMessage()); + publish(new AuditEvent(event.getAuthentication().getName(), + "AUTHENTICATION_FAILURE", data)); + } else if (event instanceof AuthenticationSwitchUserEvent) { + if (event.getAuthentication().getDetails() != null) { + data.put("details", event.getAuthentication().getDetails()); + } + data.put("target", ((AuthenticationSwitchUserEvent) event).getTargetUser() + .getUsername()); + publish(new AuditEvent(event.getAuthentication().getName(), + "AUTHENTICATION_SWITCH", data)); + + } else { + if (event.getAuthentication().getDetails() != null) { + data.put("details", event.getAuthentication().getDetails()); + } + publish(new AuditEvent(event.getAuthentication().getName(), + "AUTHENTICATION_SUCCESS", data)); + } + } + + private void publish(AuditEvent event) { + if (this.publisher != null) { + this.publisher.publishEvent(new AuditApplicationEvent(event)); + } + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthorizationAuditListener.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthorizationAuditListener.java new file mode 100644 index 0000000000..98a627b54b --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/AuthorizationAuditListener.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2013 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.bootstrap.service.security; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.bootstrap.service.audit.AuditEvent; +import org.springframework.bootstrap.service.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; + +/** + * @author Dave Syer + * + */ +public class AuthorizationAuditListener implements + ApplicationListener, ApplicationEventPublisherAware { + + private ApplicationEventPublisher publisher; + + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public void onApplicationEvent(AbstractAuthorizationEvent event) { + Map data = new HashMap(); + if (event instanceof AuthenticationCredentialsNotFoundEvent) { + data.put("type", ((AuthenticationCredentialsNotFoundEvent) event) + .getCredentialsNotFoundException().getClass().getName()); + data.put("message", ((AuthenticationCredentialsNotFoundEvent) event) + .getCredentialsNotFoundException().getMessage()); + publish(new AuditEvent("", "AUTHENTICATION_FAILURE", data)); + } else if (event instanceof AuthorizationFailureEvent) { + data.put("type", ((AuthorizationFailureEvent) event) + .getAccessDeniedException().getClass().getName()); + data.put("message", ((AuthorizationFailureEvent) event) + .getAccessDeniedException().getMessage()); + publish(new AuditEvent(((AuthorizationFailureEvent) event) + .getAuthentication().getName(), "AUTHORIZATION_FAILURE", data)); + } + } + + private void publish(AuditEvent event) { + if (this.publisher != null) { + this.publisher.publishEvent(new AuditApplicationEvent(event)); + } + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessor.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessor.java similarity index 96% rename from spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessor.java rename to spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessor.java index 6c039e9616..d849581fd4 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessor.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.bootstrap.service.trace; +package org.springframework.bootstrap.service.security; import java.io.IOException; import java.util.Collections; @@ -35,6 +35,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.bootstrap.service.trace.InMemoryTraceRepository; +import org.springframework.bootstrap.service.trace.TraceRepository; import org.springframework.core.Ordered; import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.SecurityFilterChain; diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/InMemoryTraceRepository.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/InMemoryTraceRepository.java index cf6ab2fb15..ccd72e653e 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/InMemoryTraceRepository.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/InMemoryTraceRepository.java @@ -17,11 +17,10 @@ package org.springframework.bootstrap.service.trace; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; -import org.joda.time.DateTime; - /** * @author Dave Syer * @@ -48,7 +47,7 @@ public class InMemoryTraceRepository implements TraceRepository { @Override public void add(Map map) { - Trace trace = new Trace(new DateTime(), map); + Trace trace = new Trace(new Date(), map); synchronized (this.traces) { while (this.traces.size() >= this.capacity) { this.traces.remove(0); diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/Trace.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/Trace.java index 1961843181..98ec693fa8 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/Trace.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/trace/Trace.java @@ -15,27 +15,26 @@ */ package org.springframework.bootstrap.service.trace; +import java.util.Date; import java.util.Map; -import org.joda.time.DateTime; - /** * @author Dave Syer * */ public class Trace { - private DateTime timestamp; + private Date timestamp; private Map info; - public Trace(DateTime timestamp, Map info) { + public Trace(Date timestamp, Map info) { super(); this.timestamp = timestamp; this.info = info; } - public DateTime getTimestamp() { + public Date getTimestamp() { return this.timestamp; } diff --git a/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessorTests.java b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessorTests.java similarity index 81% rename from spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessorTests.java rename to spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessorTests.java index 617515bd36..71c90179c6 100644 --- a/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/trace/SecurityFilterPostProcessorTests.java +++ b/spring-bootstrap-service/src/test/java/org/springframework/bootstrap/service/security/SecurityFilterPostProcessorTests.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.bootstrap.service.trace; +package org.springframework.bootstrap.service.security; import java.util.Map; import org.junit.Test; -import org.springframework.bootstrap.service.trace.SecurityFilterPostProcessor.WebRequestLoggingFilter; +import org.springframework.bootstrap.service.security.SecurityFilterPostProcessor; +import org.springframework.bootstrap.service.security.SecurityFilterPostProcessor.WebRequestLoggingFilter; +import org.springframework.bootstrap.service.trace.InMemoryTraceRepository; import org.springframework.mock.web.MockHttpServletRequest; import static org.junit.Assert.assertEquals;