Document Authorization Events

Issue gh-9288
This commit is contained in:
Josh Cummings 2022-03-29 15:22:38 -06:00
parent 061f69eb70
commit 04c483387e
No known key found for this signature in database
GPG Key ID: A306A51F43B8E5A5
2 changed files with 161 additions and 0 deletions

View File

@ -56,6 +56,7 @@
*** xref:servlet/authorization/secure-objects.adoc[Secure Object Implementations]
*** xref:servlet/authorization/method-security.adoc[Method Security]
*** xref:servlet/authorization/acls.adoc[Domain Object Security ACLs]
*** xref:servlet/authorization/events.adoc[Authorization Events]
** xref:servlet/oauth2/index.adoc[OAuth2]
*** xref:servlet/oauth2/login/index.adoc[OAuth2 Log In]
**** xref:servlet/oauth2/login/core.adoc[Core Configuration]

View File

@ -0,0 +1,160 @@
[[servlet-events]]
= Authorization Events
For each authorization that is denied, an `AuthorizationDeniedEvent` is fired.
Also, it's possible to fire and `AuthorizationGrantedEvent` for authorizations that are granted.
To listen for these events, you must first publish an `AuthorizationEventPublisher`.
Spring Security's `SpringAuthorizationEventPublisher` will probably do fine.
It comes publishes authorization events using Spring's `ApplicationEventPublisher`:
====
.Java
[source,java,role="primary"]
----
@Bean
public AuthorizationEventPublisher authorizationEventPublisher
(ApplicationEventPublisher applicationEventPublisher) {
return new SpringAuthorizationEventPublisher(applicationEventPublisher);
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun authorizationEventPublisher
(applicationEventPublisher: ApplicationEventPublisher?): AuthorizationEventPublisher {
return SpringAuthorizationEventPublisher(applicationEventPublisher)
}
----
====
Then, you can use Spring's `@EventListener` support:
====
.Java
[source,java,role="primary"]
----
@Component
public class AuthenticationEvents {
@EventListener
public void onFailure(AuthorizationDeniedEvent failure) {
// ...
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Component
class AuthenticationEvents {
@EventListener
fun onFailure(failure: AuthorizationDeniedEvent?) {
// ...
}
}
----
====
[[authorization-granted-events]]
== Authorization Granted Events
Because ``AuthorizationGrantedEvent``s have the potential to be quite noisy, they are not published by default.
In fact, publishing these events will likely require some business logic on your part to ensure that your application is not inundated with noisy authorization events.
You can create your own event publisher that filters success events.
For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required:
====
.Java
[source,java,role="primary"]
----
@Component
public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
private final ApplicationEventPublisher publisher;
private final AuthorizationEventPublisher delegate;
public MyAuthorizationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
this.delegate = new SpringAuthorizationEventPublisher(publisher);
}
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication,
T object, AuthorizationDecision decision) {
if (decision == null) {
return;
}
if (!decision.isGranted()) {
this.delegate.publishAuthorizationEvent(authentication, object, decision);
return;
}
if (shouldThisEventBePublished(decision)) {
AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
authentication, object, decision);
this.publisher.publishEvent(granted);
}
}
private boolean shouldThisEventBePublished(AuthorizationDecision decision) {
if (!(decision instanceof AuthorityAuthorizationDecision)) {
return false;
}
Collection<GrantedAuthority> authorities = ((AuthorityAuthorizationDecision) decision).getAuthorities();
for (GrantedAuthority authority : authorities) {
if ("ROLE_ADMIN".equals(authority.getAuthority())) {
return true;
}
}
return false;
}
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Component
class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher,
val delegate: SpringAuthorizationEventPublisher = SpringAuthorizationEventPublisher(publisher)):
AuthorizationEventPublisher {
override fun <T : Any?> publishAuthorizationEvent(
authentication: Supplier<Authentication>?,
`object`: T,
decision: AuthorizationDecision?
) {
if (decision == null) {
return
}
if (!decision.isGranted) {
this.delegate.publishAuthorizationEvent(authentication, `object`, decision)
return
}
if (shouldThisEventBePublished(decision)) {
val granted = AuthorizationGrantedEvent(authentication, `object`, decision)
this.publisher.publishEvent(granted)
}
}
private fun shouldThisEventBePublished(decision: AuthorizationDecision): Boolean {
if (decision !is AuthorityAuthorizationDecision) {
return false
}
val authorities = decision.authorities
for (authority in authorities) {
if ("ROLE_ADMIN" == authority.authority) {
return true
}
}
return false
}
}
----
====