docs
Spring Security 6.0+ provides comprehensive support for Micrometer Observation API, enabling metrics collection, tracing, and monitoring of authentication and authorization operations. This integration allows applications to track security-related metrics and distributed traces.
Core Capabilities:
ObservationAuthenticationManager and ObservationAuthorizationManagerAuthenticationObservationContext, AuthorizationObservationContextAuthenticationObservationConvention, AuthorizationObservationConventionObservationSecurityContextChangedListenerObservationReactiveAuthenticationManager, ObservationReactiveAuthorizationManagerKey Interfaces and Classes:
ObservationAuthenticationManager - Wraps AuthenticationManager with observationObservationAuthorizationManager<T> - Wraps AuthorizationManager with observationAuthenticationObservationContext - Context: getAuthenticationRequest(), getAuthenticationResult(), getError()AuthorizationObservationContext<T> - Context: getAuthentication(), getObject(), getAuthorizationResult()AuthenticationObservationConvention - Interface for customizing observation naming/tagsAuthorizationObservationConvention<T> - Interface for authorization observation namingDefault Behaviors:
Threading Model:
Lifecycle:
setObservationConvention() (optional, uses default if not set)Exceptions:
Edge Cases:
Authentication manager wrapper that records observations for authentication operations.
public class ObservationAuthenticationManager implements AuthenticationManager { .api }Description: Decorates an AuthenticationManager to publish Micrometer observations for authentication attempts.
Constructor:
public ObservationAuthenticationManager(
ObservationRegistry observationRegistry,
AuthenticationManager delegate) { .api }Parameters:
observationRegistry - Micrometer ObservationRegistry for recording observationsdelegate - The underlying AuthenticationManager to decorateMethods:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException { .api }Package: org.springframework.security.authentication
Example:
@Configuration
public class SecurityConfig {
@Bean
public AuthenticationManager authenticationManager(
ObservationRegistry observationRegistry,
AuthenticationProvider authenticationProvider) {
ProviderManager providerManager =
new ProviderManager(authenticationProvider);
// Wrap with observation support
return new ObservationAuthenticationManager(
observationRegistry,
providerManager
);
}
}Reactive authentication manager with observation support.
public class ObservationReactiveAuthenticationManager
implements ReactiveAuthenticationManager { .api }Description: Decorates a ReactiveAuthenticationManager to publish observations.
Constructor:
public ObservationReactiveAuthenticationManager(
ObservationRegistry observationRegistry,
ReactiveAuthenticationManager delegate) { .api }Methods:
public Mono<Authentication> authenticate(Authentication authentication) { .api }Package: org.springframework.security.authentication
Example:
@Configuration
public class ReactiveSecurityConfig {
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager(
ObservationRegistry observationRegistry,
ReactiveUserDetailsService userDetailsService) {
UserDetailsRepositoryReactiveAuthenticationManager manager =
new UserDetailsRepositoryReactiveAuthenticationManager(
userDetailsService
);
return new ObservationReactiveAuthenticationManager(
observationRegistry,
manager
);
}
}Context object containing authentication observation data.
public class AuthenticationObservationContext extends Observation.Context { .api }Description: Carries authentication request, result, and error information for observations.
Methods:
public Authentication getAuthenticationRequest() { .api }public Authentication getAuthenticationResult() { .api }public Throwable getError() { .api }public void setAuthenticationRequest(Authentication authentication) { .api }public void setAuthenticationResult(Authentication authentication) { .api }public void setError(Throwable error) { .api }Package: org.springframework.security.authentication
Interface for customizing authentication observation naming.
public interface AuthenticationObservationConvention
extends ObservationConvention<AuthenticationObservationContext> { .api }Description: Naming convention for authentication observations.
Methods:
String getName() { .api }String getContextualName(AuthenticationObservationContext context) { .api }KeyValues getLowCardinalityKeyValues(
AuthenticationObservationContext context) { .api }KeyValues getHighCardinalityKeyValues(
AuthenticationObservationContext context) { .api }Package: org.springframework.security.authentication
Example:
public class CustomAuthenticationObservationConvention
implements AuthenticationObservationConvention {
@Override
public String getName() {
return "custom.authentication";
}
@Override
public String getContextualName(AuthenticationObservationContext context) {
Authentication auth = context.getAuthenticationRequest();
if (auth != null) {
return auth.getClass().getSimpleName();
}
return "unknown";
}
@Override
public KeyValues getLowCardinalityKeyValues(
AuthenticationObservationContext context) {
KeyValues keyValues = KeyValues.empty();
// Add authentication type
Authentication auth = context.getAuthenticationRequest();
if (auth != null) {
keyValues = keyValues.and("auth.type",
auth.getClass().getSimpleName());
}
// Add outcome
if (context.getError() != null) {
keyValues = keyValues.and("outcome", "failure");
keyValues = keyValues.and("error.type",
context.getError().getClass().getSimpleName());
} else {
keyValues = keyValues.and("outcome", "success");
}
return keyValues;
}
@Override
public KeyValues getHighCardinalityKeyValues(
AuthenticationObservationContext context) {
// Add high cardinality data (not for metrics)
KeyValues keyValues = KeyValues.empty();
Authentication auth = context.getAuthenticationRequest();
if (auth != null && auth.getName() != null) {
keyValues = keyValues.and("username", auth.getName());
}
return keyValues;
}
}
// Configure custom convention
@Bean
public ObservationRegistry observationRegistry() {
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig()
.observationConvention(new CustomAuthenticationObservationConvention());
return registry;
}Authorization manager wrapper that records observations.
public class ObservationAuthorizationManager<T> implements AuthorizationManager<T> { .api }Description: Decorates an AuthorizationManager to publish observations for authorization checks.
Type Parameters:
T - The type of object being securedConstructor:
public ObservationAuthorizationManager(
ObservationRegistry observationRegistry,
AuthorizationManager<T> delegate) { .api }Methods:
public AuthorizationResult authorize(
Supplier<Authentication> authentication, T object) { .api }Package: org.springframework.security.authorization
Example:
@Bean
public AuthorizationManager<RequestAuthorizationContext> authorizationManager(
ObservationRegistry observationRegistry) {
AuthorityAuthorizationManager<RequestAuthorizationContext> manager =
AuthorityAuthorizationManager.hasRole("USER");
return new ObservationAuthorizationManager<>(
observationRegistry,
manager
);
}Reactive authorization manager with observation support.
public class ObservationReactiveAuthorizationManager<T>
implements ReactiveAuthorizationManager<T> { .api }Description: Decorates a ReactiveAuthorizationManager to publish observations.
Constructor:
public ObservationReactiveAuthorizationManager(
ObservationRegistry observationRegistry,
ReactiveAuthorizationManager<T> delegate) { .api }Methods:
public Mono<AuthorizationDecision> check(
Mono<Authentication> authentication, T object) { .api }Package: org.springframework.security.authorization
Context object for authorization observations.
public class AuthorizationObservationContext<T> extends Observation.Context { .api }Description: Carries authorization check data for observations.
Type Parameters:
T - Type of secured objectMethods:
public Authentication getAuthentication() { .api }public T getObject() { .api }public AuthorizationDecision getDecision() { .api }public void setAuthentication(Authentication authentication) { .api }public void setObject(T object) { .api }public void setDecision(AuthorizationDecision decision) { .api }Package: org.springframework.security.authorization
Interface for customizing authorization observation naming.
public interface AuthorizationObservationConvention<T>
extends ObservationConvention<AuthorizationObservationContext<T>> { .api }Description: Naming convention for authorization observations.
Methods:
String getName() { .api }KeyValues getLowCardinalityKeyValues(
AuthorizationObservationContext<T> context) { .api }Package: org.springframework.security.authorization
Listener that publishes observations for SecurityContext changes.
public class ObservationSecurityContextChangedListener
implements SecurityContextChangedListener { .api }Description: Records observations when SecurityContext is modified.
Constructor:
public ObservationSecurityContextChangedListener(
ObservationRegistry observationRegistry) { .api }Methods:
public void securityContextChanged(SecurityContextChangedEvent event) { .api }Package: org.springframework.security.core.context
Example:
@Bean
public SecurityContextChangedListener securityContextChangedListener(
ObservationRegistry observationRegistry) {
return new ObservationSecurityContextChangedListener(observationRegistry);
}
@Bean
public SecurityContextHolderStrategy securityContextHolderStrategy(
SecurityContextChangedListener listener) {
ListeningSecurityContextHolderStrategy strategy =
new ListeningSecurityContextHolderStrategy();
strategy.addSecurityContextChangedListener(listener);
return strategy;
}Enable observation support by configuring ObservationRegistry:
@Configuration
public class ObservationConfig {
@Bean
public ObservationRegistry observationRegistry() {
ObservationRegistry registry = ObservationRegistry.create();
// Add meters for metrics
registry.observationConfig()
.observationHandler(
new ObservationTextPublisher(System.out::println)
);
return registry;
}
}Configure full metrics collection:
@Configuration
public class MetricsConfig {
@Bean
public ObservationRegistry observationRegistry(
MeterRegistry meterRegistry) {
ObservationRegistry observationRegistry = ObservationRegistry.create();
// Add meters handler for metrics
observationRegistry.observationConfig()
.observationHandler(
new DefaultMeterObservationHandler(meterRegistry)
);
return observationRegistry;
}
@Bean
public MeterRegistry meterRegistry() {
// Use appropriate registry (Prometheus, etc.)
return new SimpleMeterRegistry();
}
}Configure distributed tracing with Micrometer Tracing:
@Configuration
public class TracingConfig {
@Bean
public ObservationRegistry observationRegistry(
Tracer tracer,
Propagator propagator) {
ObservationRegistry registry = ObservationRegistry.create();
// Add tracing handler
registry.observationConfig()
.observationHandler(
new DefaultTracingObservationHandler(
tracer,
propagator,
new CurrentTraceContext() {
@Override
public TraceContext get() {
return tracer.currentSpan().context();
}
@Override
public Scope attach(TraceContext context) {
return tracer.withSpan(
tracer.toSpan(context)
);
}
}
)
);
return registry;
}
}Configure metrics, tracing, and logging together:
@Configuration
public class ObservabilityConfig {
@Bean
public ObservationRegistry observationRegistry(
MeterRegistry meterRegistry,
Tracer tracer,
Propagator propagator) {
ObservationRegistry registry = ObservationRegistry.create();
// Configure handlers
ObservationConfig config = registry.observationConfig();
// Metrics
config.observationHandler(
new DefaultMeterObservationHandler(meterRegistry)
);
// Tracing
config.observationHandler(
new TracingAwareMeterObservationHandler<>(
new DefaultTracingObservationHandler(tracer, propagator),
meterRegistry
)
);
// Logging
config.observationHandler(
new ObservationTextPublisher(
log -> logger.info("Observation: {}", log)
)
);
return registry;
}
@Bean
public AuthenticationManager authenticationManager(
ObservationRegistry observationRegistry,
AuthenticationProvider provider) {
ProviderManager manager = new ProviderManager(provider);
return new ObservationAuthenticationManager(
observationRegistry,
manager
);
}
}Metric Name: spring.security.authentications
Tags:
authentication.type - Type of authentication (e.g., UsernamePasswordAuthenticationToken)outcome - Success or failureerror.type - Type of authentication exception (if failed)Example Queries:
# Total authentication attempts
sum(spring_security_authentications_seconds_count)
# Failed authentication rate
rate(spring_security_authentications_seconds_count{outcome="failure"}[5m])
# Average authentication duration
avg(spring_security_authentications_seconds_sum / spring_security_authentications_seconds_count)
# Authentications by type
sum by (authentication_type) (spring_security_authentications_seconds_count)Metric Name: spring.security.authorizations
Tags:
object.type - Type of secured objectauthorization.decision - Granted or deniedExample Queries:
# Authorization denial rate
rate(spring_security_authorizations_seconds_count{authorization_decision="DENIED"}[5m])
# Authorizations by object type
sum by (object_type) (spring_security_authorizations_seconds_count)# Prometheus configuration
scrape_configs:
- job_name: 'spring-security-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['localhost:8080']groups:
- name: spring_security_alerts
interval: 30s
rules:
# High authentication failure rate
- alert: HighAuthenticationFailureRate
expr: |
rate(spring_security_authentications_seconds_count{outcome="failure"}[5m])
/ rate(spring_security_authentications_seconds_count[5m])
> 0.2
for: 5m
labels:
severity: warning
annotations:
summary: "High authentication failure rate"
description: "Authentication failure rate is {{ $value | humanizePercentage }}"
# Authorization denial spike
- alert: AuthorizationDenialSpike
expr: |
rate(spring_security_authorizations_seconds_count{authorization_decision="DENIED"}[5m])
> 100
for: 2m
labels:
severity: warning
annotations:
summary: "High authorization denial rate"
# Slow authentication
- alert: SlowAuthentication
expr: |
histogram_quantile(0.95,
rate(spring_security_authentications_seconds_bucket[5m])
) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "95th percentile authentication time exceeds 1 second"Emit custom security metrics:
@Component
public class SecurityMetrics {
private final MeterRegistry meterRegistry;
private final Counter loginAttempts;
private final Counter failedLogins;
public SecurityMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.loginAttempts = Counter.builder("security.login.attempts")
.description("Total login attempts")
.register(meterRegistry);
this.failedLogins = Counter.builder("security.login.failures")
.description("Failed login attempts")
.tag("reason", "bad_credentials")
.register(meterRegistry);
}
@EventListener
public void onAuthenticationSuccess(AuthenticationSuccessEvent event) {
loginAttempts.increment();
}
@EventListener
public void onAuthenticationFailure(
AbstractAuthenticationFailureEvent event) {
loginAttempts.increment();
failedLogins.increment();
}
}Traces propagate automatically across:
Example trace hierarchy:
HTTP Request (trace ID: abc123)
├─ Authentication (span)
│ ├─ UserDetailsService.loadUserByUsername (span)
│ └─ PasswordEncoder.matches (span)
├─ Authorization (span)
│ └─ PermissionEvaluator.hasPermission (span)
└─ Business Logic (span)Add custom spans for detailed tracing:
@Service
public class CustomAuthenticationService {
private final Tracer tracer;
private final ObservationRegistry observationRegistry;
public Authentication customAuthenticate(String username, String password) {
// Create custom observation
return Observation.createNotStarted(
"custom.authentication",
observationRegistry
)
.lowCardinalityKeyValue("username", username)
.observe(() -> {
// Authentication logic with automatic span creation
return performAuthentication(username, password);
});
}
}Observations have minimal overhead:
Disable in performance-critical scenarios:
// Conditional observation
@Bean
@ConditionalOnProperty(
name = "spring.security.observation.enabled",
havingValue = "true",
matchIfMissing = true
)
public ObservationAuthenticationManager observableAuthenticationManager(...) {
// Observation-enabled manager
}