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:
parent
bd9434882f
commit
061f69eb70
|
@ -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.
|
||||
|
@ -17,16 +17,16 @@
|
|||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
|
||||
|
@ -45,7 +45,7 @@ import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
|||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
final class PrePostMethodSecurityConfiguration implements ApplicationContextAware {
|
||||
final class PrePostMethodSecurityConfiguration {
|
||||
|
||||
private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
|
||||
|
||||
|
@ -61,7 +61,8 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar
|
|||
|
||||
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
PrePostMethodSecurityConfiguration() {
|
||||
@Autowired
|
||||
PrePostMethodSecurityConfiguration(ApplicationContext context) {
|
||||
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
|
||||
this.preAuthorizeAuthorizationMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor
|
||||
.preAuthorize(this.preAuthorizeAuthorizationManager);
|
||||
|
@ -70,6 +71,10 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar
|
|||
.postAuthorize(this.postAuthorizeAuthorizationManager);
|
||||
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
|
||||
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
|
||||
this.expressionHandler.setApplicationContext(context);
|
||||
AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(context);
|
||||
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(publisher);
|
||||
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(publisher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -109,9 +114,10 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar
|
|||
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.expressionHandler.setApplicationContext(context);
|
||||
@Autowired(required = false)
|
||||
void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
|
||||
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
|
||||
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -26,7 +26,9 @@ import org.springframework.http.HttpMethod;
|
|||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
|
@ -52,12 +54,20 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
|
||||
private final AuthorizationManagerRequestMatcherRegistry registry;
|
||||
|
||||
private final AuthorizationEventPublisher publisher;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
*/
|
||||
public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
|
||||
this.registry = new AuthorizationManagerRequestMatcherRegistry(context);
|
||||
if (context.getBeanNamesForType(AuthorizationEventPublisher.class).length > 0) {
|
||||
this.publisher = context.getBean(AuthorizationEventPublisher.class);
|
||||
}
|
||||
else {
|
||||
this.publisher = new SpringAuthorizationEventPublisher(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,6 +84,7 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
|
|||
public void configure(H http) {
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
|
||||
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
|
||||
authorizationFilter.setAuthorizationEventPublisher(this.publisher);
|
||||
http.addFilter(postProcess(authorizationFilter));
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,6 +20,7 @@ import java.io.Serializable;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
@ -32,6 +33,7 @@ import org.springframework.aop.support.JdkRegexpMethodPointcut;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
@ -43,9 +45,11 @@ import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl;
|
|||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.MethodInvocationResult;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
|
@ -58,6 +62,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link PrePostMethodSecurityConfiguration}.
|
||||
|
@ -350,6 +357,27 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void preAuthorizeWhenAuthorizationEventPublisherThenUses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.preAuthorize());
|
||||
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
|
||||
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocation.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postAuthorizeWhenAuthorizationEventPublisherThenUses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire();
|
||||
this.methodSecurityService.postAnnotation("grant");
|
||||
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
|
||||
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
// gh-10305
|
||||
@WithMockUser
|
||||
@Test
|
||||
|
@ -484,4 +512,16 @@ public class PrePostMethodSecurityConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthorizationEventPublisherConfig {
|
||||
|
||||
private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class);
|
||||
|
||||
@Bean
|
||||
AuthorizationEventPublisher authorizationEventPublisher() {
|
||||
return this.publisher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,12 +16,19 @@
|
|||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
|
||||
|
@ -129,9 +136,9 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
@Test
|
||||
public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
|
||||
this.spring.register(ObjectPostProcessorConfig.class).autowire();
|
||||
verify(ObjectPostProcessorConfig.objectPostProcessor)
|
||||
.postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
|
||||
verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(AuthorizationFilter.class));
|
||||
ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
|
||||
verify(objectPostProcessor).postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
|
||||
verify(objectPostProcessor).postProcess(any(AuthorizationFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -369,6 +376,15 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenCustomAuthorizationEventPublisherThenUses() throws Exception {
|
||||
this.spring.register(AuthenticatedConfig.class, AuthorizationEventPublisherConfig.class).autowire();
|
||||
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
|
||||
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
|
||||
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenAnyRequestAuthenticatedConfiguredAndUserLoggedInThenRespondsWithOk() throws Exception {
|
||||
this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire();
|
||||
|
@ -495,7 +511,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig {
|
||||
|
||||
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
|
@ -509,8 +525,8 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
}
|
||||
|
||||
@Bean
|
||||
static ObjectPostProcessor<Object> objectPostProcessor() {
|
||||
return objectPostProcessor;
|
||||
ObjectPostProcessor<Object> objectPostProcessor() {
|
||||
return this.objectPostProcessor;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -698,6 +714,18 @@ public class AuthorizeHttpRequestsConfigurerTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthorizationEventPublisherConfig {
|
||||
|
||||
private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class);
|
||||
|
||||
@Bean
|
||||
AuthorizationEventPublisher authorizationEventPublisher() {
|
||||
return this.publisher;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class BasicController {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,70 +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.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.authorization.event.AuthorizationFailureEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationSuccessEvent;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultAuthorizationEventPublisher}
|
||||
*
|
||||
* @author Parikshit Dutta
|
||||
*/
|
||||
public class DefaultAuthorizationEventPublisherTests {
|
||||
|
||||
ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
DefaultAuthorizationEventPublisher authorizationEventPublisher;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.applicationEventPublisher = mock(ApplicationEventPublisher.class);
|
||||
this.authorizationEventPublisher = new DefaultAuthorizationEventPublisher();
|
||||
this.authorizationEventPublisher.setApplicationEventPublisher(this.applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationSuccessIsPublished() {
|
||||
this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class));
|
||||
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationSuccessEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationFailureIsPublished() {
|
||||
this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class));
|
||||
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationFailureEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullPublisherNotInvoked() {
|
||||
this.authorizationEventPublisher.setApplicationEventPublisher(null);
|
||||
this.authorizationEventPublisher.publishAuthorizationSuccess(mock(AuthorizationDecision.class));
|
||||
this.authorizationEventPublisher.publishAuthorizationFailure(mock(AuthorizationDecision.class));
|
||||
verify(this.applicationEventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringAuthorizationEventPublisher}
|
||||
*
|
||||
* @author Parikshit Dutta
|
||||
*/
|
||||
public class SpringAuthorizationEventPublisherTests {
|
||||
|
||||
Supplier<Authentication> authentication = () -> TestAuthentication.authenticatedUser();
|
||||
|
||||
ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
SpringAuthorizationEventPublisher authorizationEventPublisher;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
this.applicationEventPublisher = mock(ApplicationEventPublisher.class);
|
||||
this.authorizationEventPublisher = new SpringAuthorizationEventPublisher(this.applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationSuccessIsNotPublished() {
|
||||
AuthorizationDecision decision = new AuthorizationDecision(true);
|
||||
this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), decision);
|
||||
verifyNoInteractions(this.applicationEventPublisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticationFailureIsPublished() {
|
||||
AuthorizationDecision decision = new AuthorizationDecision(false);
|
||||
this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), decision);
|
||||
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
|
||||
}
|
||||
|
||||
}
|
|
@ -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,11 +16,20 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
@ -66,4 +75,32 @@ public class AuthorizationManagerAfterMethodInterceptorTests {
|
|||
any(MethodInvocationResult.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() {
|
||||
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
|
||||
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.setAuthorizationEventPublisher(null))
|
||||
.withMessage("eventPublisher cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable {
|
||||
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
|
||||
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
|
||||
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
|
||||
advice.setAuthorizationEventPublisher(eventPublisher);
|
||||
|
||||
SecurityContext securityContext = new SecurityContextImpl();
|
||||
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
|
||||
MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object());
|
||||
given(mockMethodInvocation.proceed()).willReturn(result.getResult());
|
||||
|
||||
advice.invoke(mockMethodInvocation);
|
||||
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,13 +16,24 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
@ -59,4 +70,32 @@ public class AuthorizationManagerBeforeMethodInterceptorTests {
|
|||
mockMethodInvocation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() {
|
||||
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.setAuthorizationEventPublisher(null))
|
||||
.withMessage("eventPublisher cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable {
|
||||
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
|
||||
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
|
||||
advice.setAuthorizationEventPublisher(eventPublisher);
|
||||
|
||||
SecurityContext securityContext = new SecurityContextImpl();
|
||||
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
|
||||
MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object());
|
||||
given(mockMethodInvocation.proceed()).willReturn(result.getResult());
|
||||
|
||||
advice.invoke(mockMethodInvocation);
|
||||
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocation.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -17,14 +17,21 @@
|
|||
package org.springframework.security.web.access.intercept;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
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.authorization.event.AuthorizationDeniedEvent;
|
||||
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
@ -41,6 +48,8 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
|||
|
||||
private final AuthorizationManager<HttpServletRequest> authorizationManager;
|
||||
|
||||
private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param authorizationManager the {@link AuthorizationManager} to use
|
||||
|
@ -54,7 +63,11 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
|||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
this.authorizationManager.verify(this::getAuthentication, request);
|
||||
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
|
||||
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
|
||||
if (decision != null && !decision.isGranted()) {
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
}
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
|
@ -67,6 +80,17 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
|||
return authentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link AuthorizationEventPublisher} to publish
|
||||
* {@link AuthorizationDeniedEvent}s and {@link AuthorizationGrantedEvent}s.
|
||||
* @param eventPublisher the {@link ApplicationEventPublisher} to use
|
||||
* @since 5.7
|
||||
*/
|
||||
public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
|
||||
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link AuthorizationManager} used by this filter
|
||||
* @return the {@link AuthorizationManager}
|
||||
|
@ -75,4 +99,9 @@ public class AuthorizationFilter extends OncePerRequestFilter {
|
|||
return this.authorizationManager;
|
||||
}
|
||||
|
||||
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
|
||||
AuthorizationDecision decision) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.apache.commons.logging.LogFactory;
|
|||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
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.web.util.matcher.RequestMatcher;
|
||||
|
@ -49,8 +48,6 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
|
|||
|
||||
private final Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings;
|
||||
|
||||
private AuthorizationEventPublisher authorizationEventPublisher;
|
||||
|
||||
private RequestMatcherDelegatingAuthorizationManager(
|
||||
Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings) {
|
||||
Assert.notEmpty(mappings, "mappings cannot be empty");
|
||||
|
@ -81,36 +78,14 @@ public final class RequestMatcherDelegatingAuthorizationManager implements Autho
|
|||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
|
||||
}
|
||||
AuthorizationDecision authorizationDecision = manager.check(authentication,
|
||||
return manager.check(authentication,
|
||||
new RequestAuthorizationContext(request, matchResult.getVariables()));
|
||||
publishAuthorizationEvent(authorizationDecision);
|
||||
return authorizationDecision;
|
||||
}
|
||||
}
|
||||
this.logger.trace("Abstaining since did not find matching RequestMatcher");
|
||||
return null;
|
||||
}
|
||||
|
||||
private void publishAuthorizationEvent(AuthorizationDecision authorizationDecision) {
|
||||
if (this.authorizationEventPublisher != null) {
|
||||
if (authorizationDecision.isGranted()) {
|
||||
this.authorizationEventPublisher.publishAuthorizationSuccess(authorizationDecision);
|
||||
}
|
||||
else {
|
||||
this.authorizationEventPublisher.publishAuthorizationFailure(authorizationDecision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set implementation of an {@link AuthorizationEventPublisher}
|
||||
* @param authorizationEventPublisher
|
||||
*/
|
||||
public void setAuthorizationEventPublisher(AuthorizationEventPublisher authorizationEventPublisher) {
|
||||
Assert.notNull(authorizationEventPublisher, "AuthorizationEventPublisher cannot be null");
|
||||
this.authorizationEventPublisher = authorizationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for {@link RequestMatcherDelegatingAuthorizationManager}.
|
||||
* @return the new {@link Builder} instance
|
||||
|
|
|
@ -31,6 +31,8 @@ import org.springframework.security.access.AccessDeniedException;
|
|||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
||||
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.SecurityContext;
|
||||
|
@ -39,8 +41,10 @@ import org.springframework.security.core.context.SecurityContextImpl;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -61,6 +65,8 @@ public class AuthorizationFilterTests {
|
|||
@Test
|
||||
public void filterWhenAuthorizationManagerVerifyPassesThenNextFilter() throws Exception {
|
||||
AuthorizationManager<HttpServletRequest> mockAuthorizationManager = mock(AuthorizationManager.class);
|
||||
given(mockAuthorizationManager.check(any(Supplier.class), any(HttpServletRequest.class)))
|
||||
.willReturn(new AuthorizationDecision(true));
|
||||
AuthorizationFilter filter = new AuthorizationFilter(mockAuthorizationManager);
|
||||
TestingAuthenticationToken authenticationToken = new TestingAuthenticationToken("user", "password");
|
||||
|
||||
|
@ -75,7 +81,7 @@ public class AuthorizationFilterTests {
|
|||
filter.doFilter(mockRequest, mockResponse, mockFilterChain);
|
||||
|
||||
ArgumentCaptor<Supplier<Authentication>> authenticationCaptor = ArgumentCaptor.forClass(Supplier.class);
|
||||
verify(mockAuthorizationManager).verify(authenticationCaptor.capture(), eq(mockRequest));
|
||||
verify(mockAuthorizationManager).check(authenticationCaptor.capture(), eq(mockRequest));
|
||||
Supplier<Authentication> authentication = authenticationCaptor.getValue();
|
||||
assertThat(authentication.get()).isEqualTo(authenticationToken);
|
||||
|
||||
|
@ -96,7 +102,7 @@ public class AuthorizationFilterTests {
|
|||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
FilterChain mockFilterChain = mock(FilterChain.class);
|
||||
|
||||
willThrow(new AccessDeniedException("Access Denied")).given(mockAuthorizationManager).verify(any(),
|
||||
willThrow(new AccessDeniedException("Access Denied")).given(mockAuthorizationManager).check(any(),
|
||||
eq(mockRequest));
|
||||
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
|
@ -104,7 +110,7 @@ public class AuthorizationFilterTests {
|
|||
.withMessage("Access Denied");
|
||||
|
||||
ArgumentCaptor<Supplier<Authentication>> authenticationCaptor = ArgumentCaptor.forClass(Supplier.class);
|
||||
verify(mockAuthorizationManager).verify(authenticationCaptor.capture(), eq(mockRequest));
|
||||
verify(mockAuthorizationManager).check(authenticationCaptor.capture(), eq(mockRequest));
|
||||
Supplier<Authentication> authentication = authenticationCaptor.getValue();
|
||||
assertThat(authentication.get()).isEqualTo(authenticationToken);
|
||||
|
||||
|
@ -132,4 +138,31 @@ public class AuthorizationFilterTests {
|
|||
assertThat(authorizationFilter.getAuthorizationManager()).isSameAs(authorizationManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenAuthorizationEventPublisherIsNullThenIllegalArgument() {
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
|
||||
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> authorizationFilter.setAuthorizationEventPublisher(null))
|
||||
.withMessage("eventPublisher cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationEventPublisherThenUses() throws Exception {
|
||||
AuthorizationFilter authorizationFilter = new AuthorizationFilter(
|
||||
AuthenticatedAuthorizationManager.authenticated());
|
||||
MockHttpServletRequest mockRequest = new MockHttpServletRequest(null, "/path");
|
||||
MockHttpServletResponse mockResponse = new MockHttpServletResponse();
|
||||
FilterChain mockFilterChain = mock(FilterChain.class);
|
||||
|
||||
SecurityContext securityContext = new SecurityContextImpl();
|
||||
securityContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"));
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
|
||||
authorizationFilter.setAuthorizationEventPublisher(eventPublisher);
|
||||
authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
|
||||
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),
|
||||
any(AuthorizationDecision.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,15 +24,12 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
|||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestMatcherDelegatingAuthorizationManager}.
|
||||
|
@ -126,40 +123,4 @@ public class RequestMatcherDelegatingAuthorizationManagerTests {
|
|||
.withMessage("mappingsConsumer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationEventPublisherIsNotNull() {
|
||||
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> manager.setAuthorizationEventPublisher(null))
|
||||
.withMessage("AuthorizationEventPublisher cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationSuccessEventWhenAuthorizationGranted() {
|
||||
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
.add(new MvcRequestMatcher(null, "/grant"), (a, o) -> new AuthorizationDecision(true)).build();
|
||||
|
||||
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
|
||||
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
|
||||
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
|
||||
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/grant"));
|
||||
verify(authorizationEventPublisher).publishAuthorizationSuccess(grant);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizationFailureEventWhenAuthorizationNotGranted() {
|
||||
RequestMatcherDelegatingAuthorizationManager manager = RequestMatcherDelegatingAuthorizationManager.builder()
|
||||
.add(new MvcRequestMatcher(null, "/deny"), (a, o) -> new AuthorizationDecision(false)).build();
|
||||
|
||||
AuthorizationEventPublisher authorizationEventPublisher = mock(AuthorizationEventPublisher.class);
|
||||
manager.setAuthorizationEventPublisher(authorizationEventPublisher);
|
||||
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
|
||||
AuthorizationDecision grant = manager.check(authentication, new MockHttpServletRequest(null, "/deny"));
|
||||
verify(authorizationEventPublisher).publishAuthorizationFailure(grant);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue