Polish Authorization Event Support

- Added spring-security-config support
- Renamed classes
- Changed contracts to include the authenticated user and secured
object
- Added method security support

Issue gh-9288
This commit is contained in:
Josh Cummings
2022-03-29 11:52:08 -06:00
parent bd9434882f
commit 061f69eb70
19 changed files with 498 additions and 239 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,14 +16,34 @@
package org.springframework.security.authorization;
import java.util.function.Supplier;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication;
/**
* A contract for publishing authorization events
*
* @author Parikshit Dutta
* @since 5.5
* @author Josh Cummings
* @since 5.7
* @see AuthorizationManager
*/
public interface AuthorizationEventPublisher {
void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision);
void publishAuthorizationFailure(AuthorizationDecision authorizationDecision);
/**
* Publish the given details in the form of an event, typically
* {@link AuthorizationGrantedEvent} or {@link AuthorizationDeniedEvent}.
*
* Note that success events can be very noisy if enabled by default. Because of this
* implementations may choose to drop success events by default.
* @param authentication a {@link Supplier} for the current user
* @param object the secured object
* @param decision the decision about whether the user may access the secured object
* @param <T> the secured object's type
*/
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision);
}

View File

@@ -1,61 +0,0 @@
/*
* Copyright 2002-2021 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
*
* https://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.security.authorization;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authorization.event.AuthorizationFailureEvent;
import org.springframework.security.authorization.event.AuthorizationSuccessEvent;
/**
* Default implementation of {@link AuthorizationEventPublisher}
*
* @author Parikshit Dutta
* @since 5.5
*/
public class DefaultAuthorizationEventPublisher implements AuthorizationEventPublisher, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
public DefaultAuthorizationEventPublisher() {
this(null);
}
public DefaultAuthorizationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision) {
if (this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new AuthorizationSuccessEvent(authorizationDecision));
}
}
@Override
public void publishAuthorizationFailure(AuthorizationDecision authorizationDecision) {
if (this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new AuthorizationFailureEvent(authorizationDecision));
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2022 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
*
* https://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.security.authorization;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An implementation of {@link AuthorizationEventPublisher} that uses Spring's event
* publishing support.
*
* Because {@link AuthorizationGrantedEvent}s typically require additional business logic
* to decide whether to publish, this implementation only publishes
* {@link AuthorizationDeniedEvent}s.
*
* @author Parikshit Dutta
* @author Josh Cummings
* @since 5.7
*/
public final class SpringAuthorizationEventPublisher implements AuthorizationEventPublisher {
private final ApplicationEventPublisher eventPublisher;
/**
* Construct this publisher using Spring's {@link ApplicationEventPublisher}
* @param eventPublisher
*/
public SpringAuthorizationEventPublisher(ApplicationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
/**
* {@inheritDoc}
*/
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
if (decision == null || decision.isGranted()) {
return;
}
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, decision);
this.eventPublisher.publishEvent(failure);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,19 +16,37 @@
package org.springframework.security.authorization.event;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
/**
* An {@link ApplicationEvent} which indicates failed authorization.
*
* @author Parikshit Dutta
* @since 5.5
* @author Josh Cummings
* @since 5.7
*/
public class AuthorizationFailureEvent extends ApplicationEvent {
public class AuthorizationDeniedEvent<T> extends ApplicationEvent {
public AuthorizationFailureEvent(AuthorizationDecision authorizationDecision) {
super(authorizationDecision);
private final Supplier<Authentication> authentication;
private final AuthorizationDecision decision;
public AuthorizationDeniedEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {
super(object);
this.authentication = authentication;
this.decision = decision;
}
public Supplier<Authentication> getAuthentication() {
return this.authentication;
}
public AuthorizationDecision getAuthorizationDecision() {
return this.decision;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,19 +16,40 @@
package org.springframework.security.authorization.event;
import java.util.function.Supplier;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
/**
* An {@link ApplicationEvent} which indicates successful authorization.
*
* @author Parikshit Dutta
* @since 5.5
* @author Josh Cummings
* @since 5.7
*/
public class AuthorizationSuccessEvent extends ApplicationEvent {
public class AuthorizationGrantedEvent<T> extends ApplicationEvent {
public AuthorizationSuccessEvent(AuthorizationDecision authorizationDecision) {
super(authorizationDecision);
private final Supplier<Authentication> authentication;
private final AuthorizationDecision decision;
public AuthorizationGrantedEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
super(object);
Assert.notNull(authentication, "authentication supplier cannot be null");
this.authentication = authentication;
this.decision = decision;
}
public Supplier<Authentication> getAuthentication() {
return this.authentication;
}
public AuthorizationDecision getAuthorizationDecision() {
return this.decision;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -33,6 +33,7 @@ import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -66,6 +67,8 @@ public final class AuthorizationManagerAfterMethodInterceptor
private int order;
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish;
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
@@ -122,6 +125,17 @@ public final class AuthorizationManagerAfterMethodInterceptor
this.order = order;
}
/**
* Use this {@link AuthorizationEventPublisher} to publish the
* {@link AuthorizationManager} result.
* @param eventPublisher
* @since 5.7
*/
public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
/**
* {@inheritDoc}
*/
@@ -142,8 +156,9 @@ public final class AuthorizationManagerAfterMethodInterceptor
private void attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER,
new MethodInvocationResult(mi, result));
MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, object);
this.eventPublisher.publishAuthorizationEvent(AUTHENTICATION_SUPPLIER, object, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
@@ -152,4 +167,9 @@ public final class AuthorizationManagerAfterMethodInterceptor
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 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.
@@ -38,6 +38,7 @@ import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -71,6 +72,8 @@ public final class AuthorizationManagerBeforeMethodInterceptor
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish;
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
@@ -168,6 +171,17 @@ public final class AuthorizationManagerBeforeMethodInterceptor
this.order = order;
}
/**
* Use this {@link AuthorizationEventPublisher} to publish the
* {@link AuthorizationManager} result.
* @param eventPublisher
* @since 5.7
*/
public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
/**
* {@inheritDoc}
*/
@@ -189,6 +203,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision = this.authorizationManager.check(AUTHENTICATION_SUPPLIER, mi);
this.eventPublisher.publishAuthorizationEvent(AUTHENTICATION_SUPPLIER, mi, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
+ this.authorizationManager + " and decision " + decision));
@@ -197,4 +212,9 @@ public final class AuthorizationManagerBeforeMethodInterceptor
this.logger.debug(LogMessage.of(() -> "Authorized method invocation " + mi));
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
}