-
Notifications
You must be signed in to change notification settings - Fork 6k
Spring Security Observability for Authentication and Authorization #11595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spring Security Observability for Authentication and Authorization #11595
Conversation
@jzheaux I took a look at the code but I couldn't figure out how a user could hook into observability using a custom Would you be able to put together a test so I can better understand? The use case is this:
|
@jgrandja, great questions. I will put together a test. In the meantime, here are some quick answers:
This PR does not add any publishing capabilities since it is based off of Spring's event-publishing infra. To publish a non-Spring Security event, use Spring's
@Bean
ApplicationListener<SecurityEvent> securityEventListener(ObservationRegistry registry) {
return DelegatingObservationSecurityEventListener.withDefaults(registry)
.add(MyEvent.class, new OAuth2AuthorizationConsentDeniedEventKeyValuesProvider()).build();
} Briefly, in Micrometer there is such a thing as an There is no formatting at this step in the flow.
Micrometer has the concept of an |
4e365f5
to
92bdd09
Compare
* @since 6.0 | ||
*/ | ||
public final class AuthenticationFailureEventKeyValuesProvider | ||
implements Observation.KeyValuesProvider<SecurityEventObservationContext<AbstractAuthenticationFailureEvent>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We removed the concept of KeyValuesProvider
and added a new one called ObservationConvention
. It is similar to the KeyValuesProvider
in a sense that it can provide low/high cardinality tags the same way but you can also specify the name and the contextual name of the observation if you want (most probably you don't need that).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds great. I updated the authentication observation to use ObservationConvention
. As for the event handling, I think since I'm now calling observation.event
, these classes don't make as much sense anymore as-is.
Please see ObservationSecurityEventListener
for details. Essentially, an observation security event can hold a KeyValues
instance to indicate which are the relevant attributes of the underlying SecurityEvent
to publish.
import static org.mockito.Mockito.spy; | ||
import static org.mockito.Mockito.verify; | ||
|
||
public class DelegatingObservationSecurityEventListenerTests { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have micrometer-observation-test
that can help you to verify your interactions with the Observation API, it has a AssertJ-style DSL.
String name = "spring.security." + event.getEventType(); | ||
Observation.Context context = SecurityEventObservationContext.fromEvent(event); | ||
Observation.createNotStarted(name, context, this.registry).keyValuesProvider(this.keyValuesProvider) | ||
.observe(() -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the goal here?
What this will do is measuring the empty code block.
I'm just guessing but I think here signaling that something happened on an existing Observation would make more sense. E.g.:
somebody starts an Observation outside of Spring Security (e.g.: and HTTP request was received) and here we can signal that something happened during that observation:
Observation observation = observationRegistry.getCurrentObservation();
if (observation != null) {
observation.event(Observation.Event.of("something.happened"));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think measuring a code block is also a scenario that makes sense for Spring Security, e.g.: instrumenting AuthenticationProvider
or any component that might need time to do its job, e.g.: using BCrypt, calling out on the network (http, ldap, jdbc, etc), reading files, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent! Glad to hear that feature is available now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a place where the PR is in flux right now, but because of existing plans to deprecate AuthenticationProvider
, instrumentation will likely go into an AuthenticationManager
implementation instead of into AuthenticationProvider
s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to AuthenticationFilter
, which all the major authentication mechanisms will change over to in the 6.0 release.
...c/main/java/org/springframework/security/authentication/DelegatingAuthenticationManager.java
Outdated
Show resolved
Hide resolved
...c/main/java/org/springframework/security/authentication/DelegatingAuthenticationManager.java
Outdated
Show resolved
Hide resolved
...c/main/java/org/springframework/security/authentication/DelegatingAuthenticationManager.java
Outdated
Show resolved
Hide resolved
@jgrandja based on some changes in Observability M4, I need to amend a few of my statements.
First, if it extends Second, if you have event metadata that you want to transmit, then you can provide a strategy that computes that metadata for a given event, like so: @Bean
ApplicationListener<SecurityEvent> securityEventListener(ObservationRegistry registry) {
Builder builder = DelegatingObservationSecurityEventListener.withDefaults(registry);
// specify the custom class and the key values strategy
builder.add(MyEvent.class, (event) -> KeyValues.of("my.key", event.getValue()));
return builder.build();
} There is no formatting at this step in the flow. |
4dba3b1
to
30a61b2
Compare
Closed in favor of #11906 |
By doing:
Then all
AuthenticationSuccessEvent
s,AuthenticationFailureEvent
s, andAuthorizationDeniedEvent
s will be published to Micrometer.AuthorizationGrantedEvent
s are not published by default in Spring Security. The event details will follow a specific schema. Also, all otherSecurityEvent
s will be published as well, though only by name. Potentially, Spring Boot could construct a defaultDelegatingObservationSecurityEventListener
instance like the above.NOTE: all security events must have a prevailing observation to attach to. For a servlet-based application, this is the Security Filter Chain observation. In addition to the Security Filter Chain observation, there is one dedicated to authentication and another to authorization.
If you want to use the authorization observation, you will need to use
AuthorizationFilter
, which is activated by usingauthorizeHttpRequests
:In that case, authorization events will be tied to the authorization observation.
Using the authentication observation is automatic as the default
AuthenticationManager
is wrapped.Note: Micrometer's
ObservationTextPublisher
will log events to the console.