Skip to content
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

ReactiveAuthorizationManager + Reactive Method Security #9867

Merged
merged 1 commit into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.security.authorization.ReactiveAuthorizationManager;

/**
*
Expand Down Expand Up @@ -69,4 +70,11 @@
*/
int order() default Ordered.LOWEST_PRECEDENCE;

/**
* Indicate whether {@link ReactiveAuthorizationManager} based Method Security to be
* used.
* @since 5.8
*/
boolean authorizationManager() default false;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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.config.annotation.method.configuration;

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.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PostAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
import org.springframework.security.config.core.GrantedAuthorityDefaults;

/**
* Configuration for a {@link ReactiveAuthenticationManager} based Method Security.
*
* @author Evgeniy Cheban
* @since 5.8
*/
@Configuration(proxyBeanMethods = false)
final class ReactiveAuthorizationManagerMethodSecurityConfiguration {

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PreFilterAuthorizationReactiveMethodInterceptor preFilter = new PreFilterAuthorizationReactiveMethodInterceptor();
preFilter.setExpressionHandler(expressionHandler);
return preFilter;
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager();
authorizationManager.setExpressionHandler(expressionHandler);
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PostFilterAuthorizationReactiveMethodInterceptor postFilter = new PostFilterAuthorizationReactiveMethodInterceptor();
postFilter.setExpressionHandler(expressionHandler);
return postFilter;
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
MethodSecurityExpressionHandler expressionHandler) {
PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager();
authorizationManager.setExpressionHandler(expressionHandler);
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
}

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
if (grantedAuthorityDefaults != null) {
handler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
return handler;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
Expand All @@ -17,37 +17,55 @@
package org.springframework.security.config.annotation.method.configuration;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.AdviceModeImportSelector;
import org.springframework.context.annotation.AutoProxyRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.NonNull;

/**
* @author Rob Winch
* @author Evgeniy Cheban
* @since 5.0
*/
class ReactiveMethodSecuritySelector extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
class ReactiveMethodSecuritySelector implements ImportSelector {

private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();

@Override
protected String[] selectImports(AdviceMode adviceMode) {
if (adviceMode == AdviceMode.PROXY) {
return getProxyImports();
public String[] selectImports(AnnotationMetadata importMetadata) {
if (!importMetadata.hasAnnotation(EnableReactiveMethodSecurity.class.getName())) {
return new String[0];
}
EnableReactiveMethodSecurity annotation = importMetadata.getAnnotations()
.get(EnableReactiveMethodSecurity.class).synthesize();
List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
if (annotation.authorizationManager()) {
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
}
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
else {
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
}
return imports.toArray(new String[0]);
}

/**
* Return the imports to use if the {@link AdviceMode} is set to
* {@link AdviceMode#PROXY}.
* <p>
* Take care of adding the necessary JSR-107 import if it is available.
*/
private String[] getProxyImports() {
List<String> result = new ArrayList<>();
result.add(AutoProxyRegistrar.class.getName());
result.add(ReactiveMethodSecurityConfiguration.class.getName());
return result.toArray(new String[0]);
private static final class AutoProxyRegistrarSelector
extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {

private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };

@Override
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
if (adviceMode == AdviceMode.PROXY) {
return IMPORTS;
}
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
Expand All @@ -16,11 +16,14 @@

package org.springframework.security.config.annotation.method.configuration;

import reactor.core.publisher.Mono;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

/**
* @author Rob Winch
* @author Evgeniy Cheban
* @since 5.0
*/
@Component
Expand All @@ -34,6 +37,10 @@ public boolean check(long id) {
return id % 2 == 0;
}

public Mono<Boolean> checkReactive(long id) {
return Mono.defer(() -> Mono.just(id % 2 == 0));
}

public boolean check(Authentication authentication, String message) {
return message != null && message.contains(authentication.getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import reactor.core.publisher.Mono;

import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;

public class DelegatingReactiveMessageService implements ReactiveMessageService {

Expand Down Expand Up @@ -60,6 +62,12 @@ public Mono<String> monoPreAuthorizeBeanFindById(long id) {
return this.delegate.monoPreAuthorizeBeanFindById(id);
}

@Override
@PreAuthorize("@authz.checkReactive(#id)")
public Mono<String> monoPreAuthorizeBeanFindByIdReactiveExpression(long id) {
return this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(id);
}

@Override
@PostAuthorize("@authz.check(authentication, returnObject)")
public Mono<String> monoPostAuthorizeBeanFindById(long id) {
Expand Down Expand Up @@ -95,6 +103,15 @@ public Flux<String> fluxPostAuthorizeBeanFindById(long id) {
return this.delegate.fluxPostAuthorizeBeanFindById(id);
}

@PreFilter("filterObject.length > 3")
@PreAuthorize("hasRole('ADMIN')")
@PostFilter("filterObject.length > 5")
@PostAuthorize("returnObject == 'harold' or returnObject == 'jonathan'")
@Override
jzheaux marked this conversation as resolved.
Show resolved Hide resolved
public Flux<String> fluxManyAnnotations(Flux<String> flux) {
return flux;
}

@Override
public Publisher<String> publisherFindById(long id) {
return this.delegate.publisherFindById(id);
Expand Down
Loading