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. |
|
|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…
Reference in New Issue
Block a user