[bs-15] Integrate audit abstraction into Spring Security setup

* By default all authentication events are passed onto the
audit listener
* Access denied exceptions are still not published by Spring
Security because of a bug in the Java config support

[Fixes #48155753]
pull/1/merge
Dave Syer 12 years ago
parent a310a79909
commit 0a730beb2a

@ -22,8 +22,9 @@ production, and in other environments.
|Logging |Logback, Log4j or JDK | Whatever is on the classpath. Sensible defaults. | |Logging |Logback, Log4j or JDK | Whatever is on the classpath. Sensible defaults. |
|Database |HSQLDB or H2 | Per classpath, or define a DataSource to override | |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. | |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 | | |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 | |Error pages | Spring MVC | Sensible defaults based on exception and status code |
|JSON |Jackson 2 | | |JSON |Jackson 2 | |
|ORM |Spring Data JPA | If on the classpath | |ORM |Spring Data JPA | If on the classpath |
@ -33,7 +34,7 @@ production, and in other environments.
# Getting Started # Getting Started
You will need Java (6 at least) and a build tool (Maven is what we use 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: downloaded or installed easily in most operating systems. FIXME:
short instructions for Mac and Linux. 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 you if the application is running and healthy. `/varz` is the default
location for the metrics endpoint - it gives you basic counts and location for the metrics endpoint - it gives you basic counts and
response timing data by default but there are plenty of ways to 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/ $ curl localhost:8080/
{"status": 404, "error": "Not Found", "message": "Not Found"} {"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 ## Adding a business endpoint
@ -146,7 +152,48 @@ and re-package:
$ curl localhost:8080/ $ curl localhost:8080/
{"message": "Hello World"} {"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:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-javaconfig</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
</dependency>
(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: Just add `spring-jdbc` and an embedded database to your dependencies:

@ -40,11 +40,6 @@
<artifactId>javax.servlet-api</artifactId> <artifactId>javax.servlet-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>1.6</version>
</dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId> <artifactId>hibernate-validator</artifactId>
@ -65,10 +60,6 @@
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-javaconfig</artifactId> <artifactId>spring-security-javaconfig</artifactId>

@ -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();
}
}

@ -27,8 +27,6 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; 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.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.context.annotation.ConditionalOnBean; import org.springframework.bootstrap.context.annotation.ConditionalOnBean;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
@ -81,7 +79,7 @@ public class MetricFilterConfiguration {
UrlPathHelper helper = new UrlPathHelper(); UrlPathHelper helper = new UrlPathHelper();
String suffix = helper.getPathWithinApplication(servletRequest); String suffix = helper.getPathWithinApplication(servletRequest);
int status = 999; int status = 999;
DateTime t0 = new DateTime(); long t0 = System.currentTimeMillis();
try { try {
chain.doFilter(request, response); chain.doFilter(request, response);
} finally { } finally {
@ -90,7 +88,7 @@ public class MetricFilterConfiguration {
} catch (Exception e) { } catch (Exception e) {
// ignore // ignore
} }
set("response", suffix, new Duration(t0, new DateTime()).getMillis()); set("response", suffix, System.currentTimeMillis() - t0);
increment("status." + status, suffix); increment("status." + status, suffix);
} }
} }

@ -24,28 +24,39 @@ import org.springframework.bootstrap.context.annotation.EnableConfigurationPrope
import org.springframework.bootstrap.service.properties.SecurityProperties; import org.springframework.bootstrap.service.properties.SecurityProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; 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.authentication.AuthenticationBuilder;
import org.springframework.security.config.annotation.web.EnableWebSecurity; import org.springframework.security.config.annotation.web.EnableWebSecurity;
import org.springframework.security.config.annotation.web.ExpressionUrlAuthorizations; import org.springframework.security.config.annotation.web.ExpressionUrlAuthorizations;
import org.springframework.security.config.annotation.web.HttpConfigurator; import org.springframework.security.config.annotation.web.HttpConfigurator;
import org.springframework.security.config.annotation.web.SpringSecurityFilterChainBuilder.IgnoredRequestRegistry; 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.security.config.annotation.web.WebSecurityConfigurerAdapter;
import org.springframework.stereotype.Component;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
@Configuration @Configuration
@ConditionalOnClass({ EnableWebSecurity.class }) @ConditionalOnClass({ EnableWebSecurity.class })
@ConditionalOnMissingBean({ WebSecurityConfiguration.class })
@EnableWebSecurity @EnableWebSecurity
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
public class SecurityConfiguration { public class SecurityConfiguration {
@Component @Bean
public static class WebSecurityAdapter extends WebSecurityConfigurerAdapter { @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}") @Value("${endpoints.healthz.path:/healthz}")
private String healthzPath = "/healthz"; private String healthzPath = "/healthz";
@ -53,6 +64,9 @@ public class SecurityConfiguration {
@Autowired @Autowired
private SecurityProperties security; private SecurityProperties security;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
@Override @Override
protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) { protected void ignoredRequests(IgnoredRequestRegistry ignoredRequests) {
ignoredRequests.antMatchers(this.healthzPath); ignoredRequests.antMatchers(this.healthzPath);
@ -69,6 +83,17 @@ public class SecurityConfiguration {
if (this.security.isRequireSsl()) { if (this.security.isRequireSsl()) {
http.requiresChannel().antMatchers("/**").requiresSecure(); 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 {
} }
} }
} }

@ -32,7 +32,6 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for service apps. * {@link EnableAutoConfiguration Auto-configuration} for service apps.
@ -42,7 +41,7 @@ import com.fasterxml.jackson.datatype.joda.JodaModule;
@Configuration @Configuration
@Import({ ManagementConfiguration.class, MetricConfiguration.class, @Import({ ManagementConfiguration.class, MetricConfiguration.class,
ServerConfiguration.class, SecurityConfiguration.class, ServerConfiguration.class, SecurityConfiguration.class,
MetricFilterConfiguration.class }) MetricFilterConfiguration.class, AuditConfiguration.class })
public class ServiceAutoConfiguration extends WebMvcConfigurationSupport { public class ServiceAutoConfiguration extends WebMvcConfigurationSupport {
@Override @Override
@ -51,7 +50,6 @@ public class ServiceAutoConfiguration extends WebMvcConfigurationSupport {
for (HttpMessageConverter<?> converter : converters) { for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) { if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter; MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter) converter;
jacksonConverter.getObjectMapper().registerModule(new JodaModule());
jacksonConverter.getObjectMapper().disable( jacksonConverter.getObjectMapper().disable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
} }

@ -24,8 +24,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass; import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; 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.InMemoryTraceRepository;
import org.springframework.bootstrap.service.trace.SecurityFilterPostProcessor;
import org.springframework.bootstrap.service.trace.TraceEndpoint; import org.springframework.bootstrap.service.trace.TraceEndpoint;
import org.springframework.bootstrap.service.trace.TraceRepository; import org.springframework.bootstrap.service.trace.TraceRepository;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;

@ -90,4 +90,10 @@ public class AuditEvent {
return result; return result;
} }
@Override
public String toString() {
return "AuditEvent [timestamp=" + this.timestamp + ", principal="
+ this.principal + ", type=" + this.type + ", data=" + this.data + "]";
}
} }

@ -15,6 +15,8 @@
*/ */
package org.springframework.bootstrap.service.audit.listener; 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.bootstrap.service.audit.AuditEventRepository;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
@ -24,6 +26,8 @@ import org.springframework.context.ApplicationListener;
*/ */
public class AuditListener implements ApplicationListener<AuditApplicationEvent> { public class AuditListener implements ApplicationListener<AuditApplicationEvent> {
private static Log logger = LogFactory.getLog(AuditListener.class);
private final AuditEventRepository auditEventRepository; private final AuditEventRepository auditEventRepository;
public AuditListener(AuditEventRepository auditEventRepository) { public AuditListener(AuditEventRepository auditEventRepository) {
@ -32,6 +36,7 @@ public class AuditListener implements ApplicationListener<AuditApplicationEvent>
@Override @Override
public void onApplicationEvent(AuditApplicationEvent event) { public void onApplicationEvent(AuditApplicationEvent event) {
logger.info(event.getAuditEvent());
this.auditEventRepository.add(event.getAuditEvent()); this.auditEventRepository.add(event.getAuditEvent());
} }

@ -16,7 +16,7 @@
package org.springframework.bootstrap.service.metrics; package org.springframework.bootstrap.service.metrics;
import org.joda.time.DateTime; import java.util.Date;
/** /**
* @author Dave Syer * @author Dave Syer
@ -35,17 +35,17 @@ public class DefaultCounterService implements CounterService {
@Override @Override
public void increment(String metricName) { public void increment(String metricName) {
this.counterRepository.increment(wrap(metricName), 1, new DateTime()); this.counterRepository.increment(wrap(metricName), 1, new Date());
} }
@Override @Override
public void decrement(String metricName) { public void decrement(String metricName) {
this.counterRepository.increment(wrap(metricName), -1, new DateTime()); this.counterRepository.increment(wrap(metricName), -1, new Date());
} }
@Override @Override
public void reset(String metricName) { 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) { private String wrap(String metricName) {

@ -16,7 +16,7 @@
package org.springframework.bootstrap.service.metrics; package org.springframework.bootstrap.service.metrics;
import org.joda.time.DateTime; import java.util.Date;
/** /**
* @author Dave Syer * @author Dave Syer
@ -35,7 +35,7 @@ public class DefaultGaugeService implements GaugeService {
@Override @Override
public void set(String metricName, double value) { 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) { private String wrap(String metricName) {

@ -18,11 +18,10 @@ package org.springframework.bootstrap.service.metrics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.joda.time.DateTime;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
@ -31,27 +30,27 @@ public class InMemoryMetricRepository implements MetricRepository {
private ConcurrentMap<String, Measurement> metrics = new ConcurrentHashMap<String, Measurement>(); private ConcurrentMap<String, Measurement> metrics = new ConcurrentHashMap<String, Measurement>();
@Override @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); Measurement current = this.metrics.get(metricName);
if (current != null) { if (current != null) {
Metric metric = current.getMetric(); Metric metric = current.getMetric();
this.metrics.replace(metricName, current, this.metrics.replace(metricName, current,
new Measurement(dateTime, metric.increment(amount))); new Measurement(timestamp, metric.increment(amount)));
} else { } else {
this.metrics.putIfAbsent(metricName, new Measurement(dateTime, new Metric( this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, amount))); metricName, amount)));
} }
} }
@Override @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); Measurement current = this.metrics.get(metricName);
if (current != null) { if (current != null) {
Metric metric = current.getMetric(); Metric metric = current.getMetric();
this.metrics.replace(metricName, current, this.metrics.replace(metricName, current,
new Measurement(dateTime, metric.set(value))); new Measurement(timestamp, metric.set(value)));
} else { } else {
this.metrics.putIfAbsent(metricName, new Measurement(dateTime, new Metric( this.metrics.putIfAbsent(metricName, new Measurement(timestamp, new Metric(
metricName, value))); metricName, value)));
} }
} }

@ -16,24 +16,24 @@
package org.springframework.bootstrap.service.metrics; package org.springframework.bootstrap.service.metrics;
import org.joda.time.DateTime; import java.util.Date;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
public class Measurement { public class Measurement {
private DateTime dateTime; private Date timestamp;
private Metric metric; private Metric metric;
public Measurement(DateTime dateTime, Metric metric) { public Measurement(Date timestamp, Metric metric) {
this.dateTime = dateTime; this.timestamp = timestamp;
this.metric = metric; this.metric = metric;
} }
public DateTime getDateTime() { public Date getTimestamp() {
return this.dateTime; return this.timestamp;
} }
public Metric getMetric() { public Metric getMetric() {
@ -42,7 +42,7 @@ public class Measurement {
@Override @Override
public String toString() { public String toString() {
return "Measurement [dateTime=" + this.dateTime + ", metric=" + this.metric + "]"; return "Measurement [dateTime=" + this.timestamp + ", metric=" + this.metric + "]";
} }
@Override @Override
@ -50,7 +50,7 @@ public class Measurement {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result 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()); result = prime * result + ((this.metric == null) ? 0 : this.metric.hashCode());
return result; return result;
} }
@ -64,10 +64,10 @@ public class Measurement {
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
Measurement other = (Measurement) obj; Measurement other = (Measurement) obj;
if (this.dateTime == null) { if (this.timestamp == null) {
if (other.dateTime != null) if (other.timestamp != null)
return false; return false;
} else if (!this.dateTime.equals(other.dateTime)) } else if (!this.timestamp.equals(other.timestamp))
return false; return false;
if (this.metric == null) { if (this.metric == null) {
if (other.metric != null) if (other.metric != null)

@ -17,17 +17,16 @@
package org.springframework.bootstrap.service.metrics; package org.springframework.bootstrap.service.metrics;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import org.joda.time.DateTime;
/** /**
* @author Dave Syer * @author Dave Syer
*/ */
public interface MetricRepository { 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); void delete(String metricName);

@ -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<AbstractAuthenticationEvent>, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
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));
}
}
}

@ -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<AbstractAuthorizationEvent>, ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void onApplicationEvent(AbstractAuthorizationEvent event) {
Map<String, Object> data = new HashMap<String, Object>();
if (event instanceof AuthenticationCredentialsNotFoundEvent) {
data.put("type", ((AuthenticationCredentialsNotFoundEvent) event)
.getCredentialsNotFoundException().getClass().getName());
data.put("message", ((AuthenticationCredentialsNotFoundEvent) event)
.getCredentialsNotFoundException().getMessage());
publish(new AuditEvent("<unknown>", "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));
}
}
}

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.service.trace; package org.springframework.bootstrap.service.security;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@ -35,6 +35,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor; 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.core.Ordered;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;

@ -17,11 +17,10 @@ package org.springframework.bootstrap.service.trace;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.joda.time.DateTime;
/** /**
* @author Dave Syer * @author Dave Syer
* *
@ -48,7 +47,7 @@ public class InMemoryTraceRepository implements TraceRepository {
@Override @Override
public void add(Map<String, Object> map) { public void add(Map<String, Object> map) {
Trace trace = new Trace(new DateTime(), map); Trace trace = new Trace(new Date(), map);
synchronized (this.traces) { synchronized (this.traces) {
while (this.traces.size() >= this.capacity) { while (this.traces.size() >= this.capacity) {
this.traces.remove(0); this.traces.remove(0);

@ -15,27 +15,26 @@
*/ */
package org.springframework.bootstrap.service.trace; package org.springframework.bootstrap.service.trace;
import java.util.Date;
import java.util.Map; import java.util.Map;
import org.joda.time.DateTime;
/** /**
* @author Dave Syer * @author Dave Syer
* *
*/ */
public class Trace { public class Trace {
private DateTime timestamp; private Date timestamp;
private Map<String, Object> info; private Map<String, Object> info;
public Trace(DateTime timestamp, Map<String, Object> info) { public Trace(Date timestamp, Map<String, Object> info) {
super(); super();
this.timestamp = timestamp; this.timestamp = timestamp;
this.info = info; this.info = info;
} }
public DateTime getTimestamp() { public Date getTimestamp() {
return this.timestamp; return this.timestamp;
} }

@ -13,12 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.bootstrap.service.trace; package org.springframework.bootstrap.service.security;
import java.util.Map; import java.util.Map;
import org.junit.Test; 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 org.springframework.mock.web.MockHttpServletRequest;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
Loading…
Cancel
Save