Document Observability Support

Issue gh-10964
This commit is contained in:
Josh Cummings 2022-10-04 15:34:16 -06:00
parent 2713075d08
commit fe96a62dfc
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
7 changed files with 482 additions and 0 deletions

View File

@ -91,6 +91,7 @@
*** xref:servlet/integrations/websocket.adoc[WebSocket]
*** xref:servlet/integrations/cors.adoc[Spring's CORS Support]
*** xref:servlet/integrations/jsp-taglibs.adoc[JSP Taglib]
*** xref:servlet/integrations/observability.adoc[Observability]
** Configuration
*** xref:servlet/configuration/java.adoc[Java Configuration]
*** xref:servlet/configuration/kotlin.adoc[Kotlin Configuration]
@ -147,6 +148,7 @@
** Integrations
*** xref:reactive/integrations/cors.adoc[CORS]
*** xref:reactive/integrations/rsocket.adoc[RSocket]
*** xref:reactive/integrations/observability.adoc[Observability]
** xref:reactive/test/index.adoc[Testing]
*** xref:reactive/test/method.adoc[Testing Method Security]
*** xref:reactive/test/web/index.adoc[Testing Web Security]

View File

@ -0,0 +1,230 @@
[[webflux-observability]]
= Observability
Spring Security integrates with Spring Observability out-of-the-box for tracing; though it's also quite simple to configure for gathering metrics.
[[webflux-observability-tracing]]
== Tracing
When an `ObservationRegistry` bean is present, Spring Security creates traces for:
* the filter chain
* the `ReactiveAuthenticationManager`, and
* the `ReactiveAuthorizationManager`
[[webflux-observability-tracing-boot]]
=== Boot Integration
For example, consider a simple Boot application:
====
.Java
[source,java,role="primary"]
----
@SpringBootApplication
public class MyApplication {
@Bean
public ReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
ObservationRegistryCustomizer<ObservationRegistry> addTextHandler() {
return (registry) -> registry.observationConfig().observationHandler(new ObservationTextHandler());
}
public static void main(String[] args) {
SpringApplication.run(ListenerSamplesApplication.class, args);
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@SpringBootApplication
class MyApplication {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
MapReactiveUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
fun addTextHandler(): ObservationRegistryCustomizer<ObservationRegistry> {
return registry: ObservationRegistry -> registry.observationConfig()
.observationHandler(ObservationTextHandler());
}
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
}
----
====
And a corresponding request:
====
[source,bash]
----
?> http -a user:password :8080
----
====
Will produce the following output (indentation added for clarity):
====
[source,bash]
----
START - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.00191856, duration(nanos)=1918560.0, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
START - name='spring.security.http.chains', contextualName='spring.security.http.chains.before', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='before'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@3932a48c', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=4.65777E-4, duration(nanos)=465777.0, startTimeNanos=101177276300777}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@562db70f']
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.before', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='before'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@3932a48c', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.003733105, duration(nanos)=3733105.0, startTimeNanos=101177276300777}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@562db70f']
START - name='spring.security.authentications', contextualName='null', error='null', lowCardinalityKeyValues=[authentication.failure.type='Optional', authentication.method='UserDetailsRepositoryReactiveAuthenticationManager', authentication.request.type='UsernamePasswordAuthenticationToken'], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@574ba6cd', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=3.21015E-4, duration(nanos)=321015.0, startTimeNanos=101177336038417}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@49202cc7']
STOP - name='spring.security.authentications', contextualName='null', error='null', lowCardinalityKeyValues=[authentication.failure.type='Optional', authentication.method='UserDetailsRepositoryReactiveAuthenticationManager', authentication.request.type='UsernamePasswordAuthenticationToken', authentication.result.type='UsernamePasswordAuthenticationToken'], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@574ba6cd', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.37574992, duration(nanos)=3.7574992E8, startTimeNanos=101177336038417}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@49202cc7']
START - name='spring.security.authorizations', contextualName='null', error='null', lowCardinalityKeyValues=[object.type='SecurityContextServerWebExchange'], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@6f837332', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=2.65687E-4, duration(nanos)=265687.0, startTimeNanos=101177777941381}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@7f5bc7cb']
STOP - name='spring.security.authorizations', contextualName='null', error='null', lowCardinalityKeyValues=[authorization.decision='true', object.type='SecurityContextServerWebExchange'], highCardinalityKeyValues=[authentication.authorities='[app]', authorization.decision.details='AuthorizationDecision [granted=true]'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@6f837332', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.039239047, duration(nanos)=3.9239047E7, startTimeNanos=101177777941381}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@7f5bc7cb']
START - name='spring.security.http.secured.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@2f33dfae', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=3.1775E-4, duration(nanos)=317750.0, startTimeNanos=101177821377592}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@63b0d28f']
STOP - name='spring.security.http.secured.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@2f33dfae', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.219901971, duration(nanos)=2.19901971E8, startTimeNanos=101177821377592}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@63b0d28f']
START - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='after'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@40b25623', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=3.25118E-4, duration(nanos)=325118.0, startTimeNanos=101178044824275}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3b6cec2']
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.size='14', filter.section='after'], highCardinalityKeyValues=[request.line='/'], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@40b25623', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.001693146, duration(nanos)=1693146.0, startTimeNanos=101178044824275}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@3b6cec2']
STOP - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.tracing.handler.TracingObservationHandler$TracingContext='io.micrometer.tracing.handler.TracingObservationHandler$TracingContext@5dfdb78', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.784320641, duration(nanos)=7.84320641E8, startTimeNanos=101177265022745}', class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@121549e0']
----
====
[[webflux-observability-tracing-manual-configuration]]
=== Manual Configuration
For a non-Spring Boot application, or to override the existing Boot configuration, you can publish your own `ObservationRegistry` and Spring Security will still pick it up.
====
.Java
[source,java,role="primary"]
----
@SpringBootApplication
public class MyApplication {
@Bean
public ReactiveUserDetailsService userDetailsService() {
return new MapReactiveUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
ObservationRegistry<ObservationRegistry> observationRegistry() {
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(new ObservationTextHandler());
return registry;
}
public static void main(String[] args) {
SpringApplication.run(ListenerSamplesApplication.class, args);
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@SpringBootApplication
class MyApplication {
@Bean
fun userDetailsService(): ReactiveUserDetailsService {
MapReactiveUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
fun observationRegistry(): ObservationRegistry<ObservationRegistry> {
ObservationRegistry registry = ObservationRegistry.create()
registry.observationConfig().observationHandler(ObservationTextHandler())
return registry
}
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
}
----
.Xml
[source,kotlin,role="secondary"]
----
<sec:http auto-config="true" observation-registry-ref="ref">
<sec:intercept-url pattern="/**" access="authenticated"/>
</sec:http>
<!-- define and configure ObservationRegistry bean -->
----
====
[[webflux-observability-tracing-disable]]
=== Disabling Observability
If you don't want any Spring Security observations, in a Spring Boot application you can publish a `ObservationRegistry.NOOP` `@Bean`.
However, this may turn off observations for more than just Spring Security.
Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
====
.Java
[source,java,role="primary"]
----
@Bean
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
ObservationPredicate predicate = (name, context) -> name.startsWith("spring.security.")
return (registry) -> registry.observationConfig().observationPredicate(predicate)
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
ObservationPredicate predicate = (name: String, context: Observation.Context) -> name.startsWith("spring.security.")
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
}
----
====
[TIP]
There is no facility for disabling observations with XML support.
Instead, simply do not set the `observation-registry-ref` attribute.
[[webflux-observability-tracing-listing]]
=== Trace Listing
Spring Security tracks the following spans on each request:
1. `spring.security.http.requests` - a span that wraps the entire filter chain, including the request
2. `spring.security.http.chains.before` - a span that wraps the receiving part of the security filters
3. `spring.security.http.chains.after` - a span that wraps the returning part of the security filters
4. `spring.security.http.secured.requests` - a span that wraps the now-secured application request
5. `spring.security.http.unsecured.requests` - a span that wraps requests that Spring Security does not secure
6. `spring.security.authentications` - a span that wraps authentication attempts
7. `spring.security.authorizations` - a span that wraps authorization attempts
[TIP]
`spring.security.http.chains.before` + `spring.security.http.secured.requests` + `spring.security.http.chains.after` = `spring.security.http.requests`
`spring.security.http.chains.before` + `spring.security.http.chains.after` = Spring Security's part of the request

View File

@ -27,6 +27,9 @@ This attribute allows you to define an alias name for the internal instance for
If set to true, the AuthenticationManager will attempt to clear any credentials data in the returned Authentication object, once the user has been authenticated.
Literally it maps to the `eraseCredentialsAfterAuthentication` property of the xref:servlet/authentication/architecture.adoc#servlet-authentication-providermanager[`ProviderManager`].
[[nsa-authentication-manager-observation-registry-ref]]
* **observation-registry-ref**
A reference to the `ObservationRegistry` used for the `FilterChain` and related components
[[nsa-authentication-manager-id]]
* **id**

View File

@ -47,6 +47,9 @@ By default an `AffirmativeBased` implementation is used for with a `RoleVoter` a
* **authentication-manager-ref**
A reference to the `AuthenticationManager` used for the `FilterChain` created by this http element.
[[nsa-http-observation-registry-ref]]
* **observation-registry-ref**
A reference to the `ObservationRegistry` used for the `FilterChain` and related components
[[nsa-http-auto-config]]
* **auto-config**

View File

@ -37,6 +37,10 @@ Defaults to "false".
Specifies a SecurityContextHolderStrategy to use when retrieving the SecurityContext.
Defaults to the value returned by SecurityContextHolder.getContextHolderStrategy().
[[nsa-method-security-observation-registry-ref]]
* **observation-registry-ref**
A reference to the `ObservationRegistry` used for the `FilterChain` and related components
[[nsa-method-security-children]]
=== Child Elements of <method-security>

View File

@ -0,0 +1,235 @@
[[observability]]
= Observability
Spring Security integrates with Spring Observability out-of-the-box for tracing; though it's also quite simple to configure for gathering metrics.
[[observability-tracing]]
== Tracing
When an `ObservationRegistry` bean is present, Spring Security creates traces for:
* the filter chain
* the `AuthenticationManager`, and
* the `AuthorizationManager`
[[observability-tracing-boot]]
=== Boot Integration
For example, consider a simple Boot application:
====
.Java
[source,java,role="primary"]
----
@SpringBootApplication
public class MyApplication {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
ObservationRegistryCustomizer<ObservationRegistry> addTextHandler() {
return (registry) -> registry.observationConfig().observationHandler(new ObservationTextHandler());
}
public static void main(String[] args) {
SpringApplication.run(ListenerSamplesApplication.class, args);
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@SpringBootApplication
class MyApplication {
@Bean
fun userDetailsService(): UserDetailsService {
InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
fun addTextHandler(): ObservationRegistryCustomizer<ObservationRegistry> {
return registry: ObservationRegistry -> registry.observationConfig()
.observationHandler(ObservationTextHandler());
}
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
}
----
====
And a corresponding request:
====
[source,bash]
----
?> http -a user:password :8080
----
====
Will produce the following output (indentation added for clarity):
====
[source,bash]
----
START - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@687e16d1', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.001779024, duration(nanos)=1779024.0, startTimeNanos=91695917264958}']
START - name='spring.security.http.chains', contextualName='spring.security.http.chains.before', error='null', lowCardinalityKeyValues=[chain.position='0', chain.size='17', filter.section='before'], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@79f554a5', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=7.42147E-4, duration(nanos)=742147.0, startTimeNanos=91695947182029}']
... skipped for brevity ...
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.before', error='null', lowCardinalityKeyValues=[chain.position='0', chain.size='17', filter.section='before'], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@79f554a5', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.014771848, duration(nanos)=1.4771848E7, startTimeNanos=91695947182029}']
START - name='spring.security.authentications', contextualName='null', error='null', lowCardinalityKeyValues=[authentication.failure.type='Optional', authentication.method='ProviderManager', authentication.request.type='UsernamePasswordAuthenticationToken'], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@4d4b2b56', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=7.09759E-4, duration(nanos)=709759.0, startTimeNanos=91696094477504}']
... skipped for brevity ...
STOP - name='spring.security.authentications', contextualName='null', error='null', lowCardinalityKeyValues=[authentication.failure.type='Optional', authentication.method='ProviderManager', authentication.request.type='UsernamePasswordAuthenticationToken', authentication.result.type='UsernamePasswordAuthenticationToken'], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@4d4b2b56', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.895141386, duration(nanos)=8.95141386E8, startTimeNanos=91696094477504}']
START - name='spring.security.authorizations', contextualName='null', error='null', lowCardinalityKeyValues=[object.type='Servlet3SecurityContextHolderAwareRequestWrapper'], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@6d834cc7', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=3.0965E-4, duration(nanos)=309650.0, startTimeNanos=91697034893983}']
... skipped for brevity ...
STOP - name='spring.security.authorizations', contextualName='null', error='null', lowCardinalityKeyValues=[authorization.decision='true', object.type='Servlet3SecurityContextHolderAwareRequestWrapper'], highCardinalityKeyValues=[authentication.authorities='[app]', authorization.decision.details='AuthorizationDecision [granted=true]'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@6d834cc7', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.02084809, duration(nanos)=2.084809E7, startTimeNanos=91697034893983}']
START - name='spring.security.http.secured.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@649c5ec3', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=2.67878E-4, duration(nanos)=267878.0, startTimeNanos=91697059819304}']
... skipped for brevity ...
STOP - name='spring.security.http.secured.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@649c5ec3', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.090753322, duration(nanos)=9.0753322E7, startTimeNanos=91697059819304}']
START - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.position='0', chain.size='17', filter.section='after'], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@47af8207', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=5.31832E-4, duration(nanos)=531832.0, startTimeNanos=91697152857268}']
... skipped for brevity ...
STOP - name='spring.security.http.chains', contextualName='spring.security.http.chains.after', error='null', lowCardinalityKeyValues=[chain.position='17', chain.size='17', current.filter.name='DisableEncodeUrlFilter', filter.section='after'], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@47af8207', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=0.007689382, duration(nanos)=7689382.0, startTimeNanos=91697152857268}']
STOP - name='http.server.requests', contextualName='null', error='null', lowCardinalityKeyValues=[], highCardinalityKeyValues=[request.line='GET /'], map=[class io.micrometer.core.instrument.Timer$Sample='io.micrometer.core.instrument.Timer$Sample@687e16d1', class io.micrometer.core.instrument.LongTaskTimer$Sample='SampleImpl{duration(seconds)=1.245858319, duration(nanos)=1.245858319E9, startTimeNanos=91695917264958}']
----
====
[[observability-tracing-manual-configuration]]
=== Manual Configuration
For a non-Spring Boot application, or to override the existing Boot configuration, you can publish your own `ObservationRegistry` and Spring Security will still pick it up.
====
.Java
[source,java,role="primary"]
----
@SpringBootApplication
public class MyApplication {
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
ObservationRegistry<ObservationRegistry> observationRegistry() {
ObservationRegistry registry = ObservationRegistry.create();
registry.observationConfig().observationHandler(new ObservationTextHandler());
return registry;
}
public static void main(String[] args) {
SpringApplication.run(ListenerSamplesApplication.class, args);
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@SpringBootApplication
class MyApplication {
@Bean
fun userDetailsService(): UserDetailsService {
InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.authorities("app")
.build()
);
}
@Bean
fun observationRegistry(): ObservationRegistry<ObservationRegistry> {
ObservationRegistry registry = ObservationRegistry.create()
registry.observationConfig().observationHandler(ObservationTextHandler())
return registry
}
fun main(args: Array<String>) {
runApplication<MyApplication>(*args)
}
}
----
.Xml
[source,kotlin,role="secondary"]
----
<sec:http auto-config="true" observation-registry-ref="ref">
<sec:intercept-url pattern="/**" access="authenticated"/>
</sec:http>
<!-- define and configure ObservationRegistry bean -->
----
====
[[observability-tracing-disable]]
==== Disabling Observability
If you don't want any Spring Security observations, in a Spring Boot application you can publish a `ObservationRegistry.NOOP` `@Bean`.
However, this may turn off observations for more than just Spring Security.
Instead, you can alter the provided `ObservationRegistry` with an `ObservationPredicate` like the following:
====
.Java
[source,java,role="primary"]
----
@Bean
ObservationRegistryCustomizer<ObservationRegistry> noSpringSecurityObservations() {
ObservationPredicate predicate = (name, context) -> name.startsWith("spring.security.")
return (registry) -> registry.observationConfig().observationPredicate(predicate)
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun noSpringSecurityObservations(): ObservationRegistryCustomizer<ObservationRegistry> {
ObservationPredicate predicate = (name: String, context: Observation.Context) -> name.startsWith("spring.security.")
(registry: ObservationRegistry) -> registry.observationConfig().observationPredicate(predicate)
}
----
====
[TIP]
There is no facility for disabling observations with XML support.
Instead, simply do not set the `observation-registry-ref` attribute.
[[observability-tracing-listing]]
=== Trace Listing
Spring Security tracks the following spans on each request:
1. `spring.security.http.requests` - a span that wraps the entire filter chain, including the request
2. `spring.security.http.chains.before` - a span that wraps the receiving part of the security filters
3. `spring.security.http.chains.after` - a span that wraps the returning part of the security filters
4. `spring.security.http.secured.requests` - a span that wraps the now-secured application request
5. `spring.security.http.unsecured.requests` - a span that wraps requests that Spring Security does not secure
6. `spring.security.authentications` - a span that wraps authentication attempts
7. `spring.security.authorizations` - a span that wraps authorization attempts
[TIP]
`spring.security.http.chains.before` + `spring.security.http.secured.requests` + `spring.security.http.chains.after` = `spring.security.http.requests`
`spring.security.http.chains.before` + `spring.security.http.chains.after` = Spring Security's part of the request

View File

@ -29,3 +29,8 @@ Or use `use-authorization-manager="false"`
* https://github.com/spring-projects/spring-security/issues/11939[gh-11939] - Remove deprecated `antMatchers`, `mvcMatchers`, `regexMatchers` helper methods from Java Configuration.
Instead, use `requestMatchers` or `HttpSecurity#securityMatchers`.
* https://github.com/spring-projects/spring-security/issues/11985[gh-11985] - Remove deprecated constructors in `Argon2PasswordEncoder`, `SCryptPasswordEncoder` and `Pbkdf2PasswordEncoder`.
== Observability
* xref:servlet/integrations/observability.adoc[Instrumentation] of `AuthenticationManager`, `AuthorizationManager`, and `FilterChainProxy`
* xref:reactive/integrations/observability.adoc[Instrumentation] of `ReactiveAuthenticationManager`, `ReactiveAuthorizationManager`, and `WebFilterChainProxy`