mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-07-05 00:56:58 +08:00
[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]
This commit is contained in:
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. |
|
||||
|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:
|
||||
|
||||
<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:
|
||||
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
<version>1.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
|
@ -65,10 +60,6 @@
|
|||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-joda</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<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.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<AuditApplicationEvent> {
|
||||
|
||||
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<AuditApplicationEvent>
|
|||
|
||||
@Override
|
||||
public void onApplicationEvent(AuditApplicationEvent event) {
|
||||
logger.info(event.getAuditEvent());
|
||||
this.auditEventRepository.add(event.getAuditEvent());
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<String, Measurement> metrics = new ConcurrentHashMap<String, Measurement>();
|
||||
|
||||
@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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
* 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;
|
|
@ -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<String, Object> 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);
|
||||
|
|
|
@ -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<String, Object> info;
|
||||
|
||||
public Trace(DateTime timestamp, Map<String, Object> info) {
|
||||
public Trace(Date timestamp, Map<String, Object> info) {
|
||||
super();
|
||||
this.timestamp = timestamp;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public DateTime getTimestamp() {
|
||||
public Date getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
Loading…
Reference in New Issue
Block a user