Skip to content

Commit 0039cca

Browse files
committed
ReactiveAuthorizationManager + Reactive Method Security
Closes gh-9401
1 parent ce67fb0 commit 0039cca

File tree

38 files changed

+3231
-185
lines changed

38 files changed

+3231
-185
lines changed

config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.annotation.Configuration;
2727
import org.springframework.context.annotation.Import;
2828
import org.springframework.core.Ordered;
29+
import org.springframework.security.authorization.ReactiveAuthorizationManager;
2930

3031
/**
3132
*
@@ -69,4 +70,11 @@
6970
*/
7071
int order() default Ordered.LOWEST_PRECEDENCE;
7172

73+
/**
74+
* Indicate whether {@link ReactiveAuthorizationManager} based Method Security to be
75+
* used.
76+
* @since 5.8
77+
*/
78+
boolean authorizationManager() default false;
79+
7280
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config.annotation.method.configuration;
18+
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.beans.factory.config.BeanDefinition;
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Role;
24+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
25+
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
26+
import org.springframework.security.authentication.ReactiveAuthenticationManager;
27+
import org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor;
28+
import org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor;
29+
import org.springframework.security.authorization.method.PostAuthorizeReactiveAuthorizationManager;
30+
import org.springframework.security.authorization.method.PostFilterAuthorizationReactiveMethodInterceptor;
31+
import org.springframework.security.authorization.method.PreAuthorizeReactiveAuthorizationManager;
32+
import org.springframework.security.authorization.method.PreFilterAuthorizationReactiveMethodInterceptor;
33+
import org.springframework.security.config.core.GrantedAuthorityDefaults;
34+
35+
/**
36+
* Configuration for a {@link ReactiveAuthenticationManager} based Method Security.
37+
*
38+
* @author Evgeniy Cheban
39+
* @since 5.8
40+
*/
41+
@Configuration(proxyBeanMethods = false)
42+
final class ReactiveAuthorizationManagerMethodSecurityConfiguration {
43+
44+
@Bean
45+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
46+
PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor(
47+
MethodSecurityExpressionHandler expressionHandler) {
48+
PreFilterAuthorizationReactiveMethodInterceptor preFilter = new PreFilterAuthorizationReactiveMethodInterceptor();
49+
preFilter.setExpressionHandler(expressionHandler);
50+
return preFilter;
51+
}
52+
53+
@Bean
54+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
55+
AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor(
56+
MethodSecurityExpressionHandler expressionHandler) {
57+
PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager();
58+
authorizationManager.setExpressionHandler(expressionHandler);
59+
return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager);
60+
}
61+
62+
@Bean
63+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
64+
PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor(
65+
MethodSecurityExpressionHandler expressionHandler) {
66+
PostFilterAuthorizationReactiveMethodInterceptor postFilter = new PostFilterAuthorizationReactiveMethodInterceptor();
67+
postFilter.setExpressionHandler(expressionHandler);
68+
return postFilter;
69+
}
70+
71+
@Bean
72+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
73+
AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor(
74+
MethodSecurityExpressionHandler expressionHandler) {
75+
PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager();
76+
authorizationManager.setExpressionHandler(expressionHandler);
77+
return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager);
78+
}
79+
80+
@Bean
81+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
82+
DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
83+
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
84+
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
85+
if (grantedAuthorityDefaults != null) {
86+
handler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
87+
}
88+
return handler;
89+
}
90+
91+
}
Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,37 +17,55 @@
1717
package org.springframework.security.config.annotation.method.configuration;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.List;
2122

2223
import org.springframework.context.annotation.AdviceMode;
2324
import org.springframework.context.annotation.AdviceModeImportSelector;
2425
import org.springframework.context.annotation.AutoProxyRegistrar;
26+
import org.springframework.context.annotation.ImportSelector;
27+
import org.springframework.core.type.AnnotationMetadata;
28+
import org.springframework.lang.NonNull;
2529

2630
/**
2731
* @author Rob Winch
32+
* @author Evgeniy Cheban
2833
* @since 5.0
2934
*/
30-
class ReactiveMethodSecuritySelector extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
35+
class ReactiveMethodSecuritySelector implements ImportSelector {
36+
37+
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
3138

3239
@Override
33-
protected String[] selectImports(AdviceMode adviceMode) {
34-
if (adviceMode == AdviceMode.PROXY) {
35-
return getProxyImports();
40+
public String[] selectImports(AnnotationMetadata importMetadata) {
41+
if (!importMetadata.hasAnnotation(EnableReactiveMethodSecurity.class.getName())) {
42+
return new String[0];
43+
}
44+
EnableReactiveMethodSecurity annotation = importMetadata.getAnnotations()
45+
.get(EnableReactiveMethodSecurity.class).synthesize();
46+
List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
47+
if (annotation.authorizationManager()) {
48+
imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName());
3649
}
37-
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
50+
else {
51+
imports.add(ReactiveMethodSecurityConfiguration.class.getName());
52+
}
53+
return imports.toArray(new String[0]);
3854
}
3955

40-
/**
41-
* Return the imports to use if the {@link AdviceMode} is set to
42-
* {@link AdviceMode#PROXY}.
43-
* <p>
44-
* Take care of adding the necessary JSR-107 import if it is available.
45-
*/
46-
private String[] getProxyImports() {
47-
List<String> result = new ArrayList<>();
48-
result.add(AutoProxyRegistrar.class.getName());
49-
result.add(ReactiveMethodSecurityConfiguration.class.getName());
50-
return result.toArray(new String[0]);
56+
private static final class AutoProxyRegistrarSelector
57+
extends AdviceModeImportSelector<EnableReactiveMethodSecurity> {
58+
59+
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
60+
61+
@Override
62+
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
63+
if (adviceMode == AdviceMode.PROXY) {
64+
return IMPORTS;
65+
}
66+
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported");
67+
}
68+
5169
}
5270

5371
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/Authz.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,14 @@
1616

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

19+
import reactor.core.publisher.Mono;
20+
1921
import org.springframework.security.core.Authentication;
2022
import org.springframework.stereotype.Component;
2123

2224
/**
2325
* @author Rob Winch
26+
* @author Evgeniy Cheban
2427
* @since 5.0
2528
*/
2629
@Component
@@ -34,6 +37,10 @@ public boolean check(long id) {
3437
return id % 2 == 0;
3538
}
3639

40+
public Mono<Boolean> checkReactive(long id) {
41+
return Mono.defer(() -> Mono.just(id % 2 == 0));
42+
}
43+
3744
public boolean check(Authentication authentication, String message) {
3845
return message != null && message.contains(authentication.getName());
3946
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/DelegatingReactiveMessageService.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
import reactor.core.publisher.Mono;
2222

2323
import org.springframework.security.access.prepost.PostAuthorize;
24+
import org.springframework.security.access.prepost.PostFilter;
2425
import org.springframework.security.access.prepost.PreAuthorize;
26+
import org.springframework.security.access.prepost.PreFilter;
2527

2628
public class DelegatingReactiveMessageService implements ReactiveMessageService {
2729

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

65+
@Override
66+
@PreAuthorize("@authz.checkReactive(#id)")
67+
public Mono<String> monoPreAuthorizeBeanFindByIdReactiveExpression(long id) {
68+
return this.delegate.monoPreAuthorizeBeanFindByIdReactiveExpression(id);
69+
}
70+
6371
@Override
6472
@PostAuthorize("@authz.check(authentication, returnObject)")
6573
public Mono<String> monoPostAuthorizeBeanFindById(long id) {
@@ -95,6 +103,15 @@ public Flux<String> fluxPostAuthorizeBeanFindById(long id) {
95103
return this.delegate.fluxPostAuthorizeBeanFindById(id);
96104
}
97105

106+
@PreFilter("filterObject.length > 3")
107+
@PreAuthorize("hasRole('ADMIN')")
108+
@PostFilter("filterObject.length > 5")
109+
@PostAuthorize("returnObject == 'harold' or returnObject == 'jonathan'")
110+
@Override
111+
public Flux<String> fluxManyAnnotations(Flux<String> flux) {
112+
return flux;
113+
}
114+
98115
@Override
99116
public Publisher<String> publisherFindById(long id) {
100117
return this.delegate.publisherFindById(id);

0 commit comments

Comments
 (0)