Skip to content

Commit 52dfbfb

Browse files
committed
Add Authorization Proxy Support
Closes gh-14596
1 parent c0928bf commit 52dfbfb

File tree

16 files changed

+1360
-27
lines changed

16 files changed

+1360
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2002-2024 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 java.util.ArrayList;
20+
import java.util.List;
21+
22+
import org.springframework.aop.framework.AopInfrastructureBean;
23+
import org.springframework.beans.factory.ObjectProvider;
24+
import org.springframework.beans.factory.config.BeanDefinition;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.context.annotation.Role;
28+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
29+
import org.springframework.security.authorization.AuthorizationAdvisorProxyFactory;
30+
import org.springframework.security.authorization.method.AuthorizationAdvisor;
31+
32+
@Configuration(proxyBeanMethods = false)
33+
final class AuthorizationProxyConfiguration implements AopInfrastructureBean {
34+
35+
@Bean
36+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
37+
static AuthorizationAdvisorProxyFactory authorizationProxyFactory(ObjectProvider<AuthorizationAdvisor> provider) {
38+
List<AuthorizationAdvisor> advisors = new ArrayList<>();
39+
provider.forEach(advisors::add);
40+
AnnotationAwareOrderComparator.sort(advisors);
41+
return new AuthorizationAdvisorProxyFactory(advisors);
42+
}
43+
44+
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.aopalliance.intercept.MethodInterceptor;
2121
import org.aopalliance.intercept.MethodInvocation;
2222

23+
import org.springframework.aop.framework.AopInfrastructureBean;
2324
import org.springframework.beans.factory.ObjectProvider;
2425
import org.springframework.beans.factory.config.BeanDefinition;
2526
import org.springframework.context.annotation.Bean;
@@ -48,7 +49,7 @@
4849
*/
4950
@Configuration(proxyBeanMethods = false)
5051
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
51-
final class Jsr250MethodSecurityConfiguration implements ImportAware {
52+
final class Jsr250MethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
5253

5354
private int interceptorOrderOffset;
5455

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

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
5656
if (annotation.jsr250Enabled()) {
5757
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
5858
}
59+
imports.add(AuthorizationProxyConfiguration.class.getName());
5960
return imports.toArray(new String[0]);
6061
}
6162

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

+4-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.jetbrains.annotations.Nullable;
2828

2929
import org.springframework.aop.Pointcut;
30-
import org.springframework.aop.PointcutAdvisor;
3130
import org.springframework.aop.framework.AopInfrastructureBean;
3231
import org.springframework.beans.factory.ObjectProvider;
3332
import org.springframework.beans.factory.config.BeanDefinition;
@@ -36,14 +35,14 @@
3635
import org.springframework.context.annotation.Configuration;
3736
import org.springframework.context.annotation.ImportAware;
3837
import org.springframework.context.annotation.Role;
39-
import org.springframework.core.Ordered;
4038
import org.springframework.core.type.AnnotationMetadata;
4139
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
4240
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
4341
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
4442
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
4543
import org.springframework.security.authorization.AuthorizationEventPublisher;
4644
import org.springframework.security.authorization.AuthorizationManager;
45+
import org.springframework.security.authorization.method.AuthorizationAdvisor;
4746
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
4847
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
4948
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
@@ -65,7 +64,7 @@
6564
*/
6665
@Configuration(proxyBeanMethods = false)
6766
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
68-
final class PrePostMethodSecurityConfiguration implements ImportAware {
67+
final class PrePostMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
6968

7069
private int interceptorOrderOffset;
7170

@@ -175,8 +174,8 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
175174
this.interceptorOrderOffset = annotation.offset();
176175
}
177176

178-
private static final class DeferringMethodInterceptor<M extends Ordered & MethodInterceptor & PointcutAdvisor>
179-
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
177+
private static final class DeferringMethodInterceptor<M extends AuthorizationAdvisor>
178+
implements AuthorizationAdvisor {
180179

181180
private final Pointcut pointcut;
182181

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.aopalliance.intercept.MethodInterceptor;
2121
import org.aopalliance.intercept.MethodInvocation;
2222

23+
import org.springframework.aop.framework.AopInfrastructureBean;
2324
import org.springframework.beans.factory.ObjectProvider;
2425
import org.springframework.beans.factory.config.BeanDefinition;
2526
import org.springframework.context.annotation.Bean;
@@ -48,7 +49,7 @@
4849
*/
4950
@Configuration(proxyBeanMethods = false)
5051
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
51-
final class SecuredMethodSecurityConfiguration implements ImportAware {
52+
final class SecuredMethodSecurityConfiguration implements ImportAware, AopInfrastructureBean {
5253

5354
private int interceptorOrderOffset;
5455

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2002-2024 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.junit.jupiter.api.Test;
20+
import org.junit.jupiter.api.extension.ExtendWith;
21+
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.security.access.AccessDeniedException;
25+
import org.springframework.security.access.prepost.PostAuthorize;
26+
import org.springframework.security.access.prepost.PreAuthorize;
27+
import org.springframework.security.authorization.AuthorizationProxyFactory;
28+
import org.springframework.security.config.test.SpringTestContext;
29+
import org.springframework.security.config.test.SpringTestContextExtension;
30+
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
31+
import org.springframework.security.test.context.support.WithMockUser;
32+
import org.springframework.test.context.junit.jupiter.SpringExtension;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
36+
37+
/**
38+
* Tests for {@link PrePostMethodSecurityConfiguration}.
39+
*
40+
* @author Evgeniy Cheban
41+
* @author Josh Cummings
42+
*/
43+
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class })
44+
@SecurityTestExecutionListeners
45+
public class AuthorizationProxyConfigurationTests {
46+
47+
public final SpringTestContext spring = new SpringTestContext(this);
48+
49+
@Autowired
50+
AuthorizationProxyFactory proxyFactory;
51+
52+
@WithMockUser
53+
@Test
54+
public void proxyWhenNotPreAuthorizedThenDenies() {
55+
this.spring.register(DefaultsConfig.class).autowire();
56+
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
57+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::makeToast)
58+
.withMessage("Access Denied");
59+
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(toaster::extractBread)
60+
.withMessage("Access Denied");
61+
}
62+
63+
@WithMockUser(roles = "ADMIN")
64+
@Test
65+
public void proxyWhenPreAuthorizedThenAllows() {
66+
this.spring.register(DefaultsConfig.class).autowire();
67+
Toaster toaster = (Toaster) this.proxyFactory.proxy(new Toaster());
68+
toaster.makeToast();
69+
assertThat(toaster.extractBread()).isEqualTo("yummy");
70+
}
71+
72+
@EnableMethodSecurity
73+
@Configuration
74+
static class DefaultsConfig {
75+
76+
}
77+
78+
static class Toaster {
79+
80+
@PreAuthorize("hasRole('ADMIN')")
81+
void makeToast() {
82+
83+
}
84+
85+
@PostAuthorize("hasRole('ADMIN')")
86+
String extractBread() {
87+
return "yummy";
88+
}
89+
90+
}
91+
92+
}

0 commit comments

Comments
 (0)