Skip to content

Commit 94e049d

Browse files
evgeniychebanjzheaux
authored andcommitted
Consider AuthorizationManager for Method Security
Closes gh-9289
1 parent f7f435d commit 94e049d

File tree

35 files changed

+3682
-5
lines changed

35 files changed

+3682
-5
lines changed

config/spring-security-config.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ dependencies {
3737
optional'org.springframework:spring-websocket'
3838
optional 'org.jetbrains.kotlin:kotlin-reflect'
3939
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
40+
optional 'javax.annotation:jsr250-api'
4041

4142
provided 'javax.servlet:javax.servlet-api'
4243

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2002-2021 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.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.AdviceMode;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.context.annotation.Import;
28+
import org.springframework.core.Ordered;
29+
import org.springframework.security.access.annotation.Secured;
30+
31+
/**
32+
* Enables Spring Security Method Security.
33+
* @author Evgeniy Cheban
34+
* @since 5.5
35+
*/
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Target(ElementType.TYPE)
38+
@Documented
39+
@Import(MethodSecuritySelector.class)
40+
@Configuration
41+
public @interface EnableMethodSecurity {
42+
43+
/**
44+
* Determines if Spring Security's {@link Secured} annotation should be enabled.
45+
* Default is false.
46+
* @return true if {@link Secured} annotation should be enabled false otherwise
47+
*/
48+
boolean securedEnabled() default false;
49+
50+
/**
51+
* Determines if JSR-250 annotations should be enabled. Default is false.
52+
* @return true if JSR-250 should be enabled false otherwise
53+
*/
54+
boolean jsr250Enabled() default false;
55+
56+
/**
57+
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
58+
* standard Java interface-based proxies. The default is {@code false}. <strong>
59+
* Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}</strong>.
60+
* <p>
61+
* Note that setting this attribute to {@code true} will affect <em>all</em>
62+
* Spring-managed beans requiring proxying, not just those marked with
63+
* {@code @Cacheable}. For example, other beans marked with Spring's
64+
* {@code @Transactional} annotation will be upgraded to subclass proxying at the same
65+
* time. This approach has no negative impact in practice unless one is explicitly
66+
* expecting one type of proxy vs another, e.g. in tests.
67+
* @return true if subclass-based (CGLIB) proxies are to be created
68+
*/
69+
boolean proxyTargetClass() default false;
70+
71+
/**
72+
* Indicate how security advice should be applied. The default is
73+
* {@link AdviceMode#PROXY}.
74+
* @see AdviceMode
75+
* @return the {@link AdviceMode} to use
76+
*/
77+
AdviceMode mode() default AdviceMode.PROXY;
78+
79+
/**
80+
* Indicate the ordering of the execution of the security advisor when multiple
81+
* advices are applied at a specific joinpoint. The default is
82+
* {@link Ordered#LOWEST_PRECEDENCE}.
83+
* @return the order the security advisor should be applied
84+
*/
85+
int order() default Ordered.LOWEST_PRECEDENCE;
86+
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/*
2+
* Copyright 2002-2021 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.lang.annotation.Annotation;
20+
import java.lang.reflect.AnnotatedElement;
21+
import java.lang.reflect.Method;
22+
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.HashSet;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Set;
28+
29+
import javax.annotation.security.DenyAll;
30+
import javax.annotation.security.PermitAll;
31+
import javax.annotation.security.RolesAllowed;
32+
33+
import org.springframework.aop.MethodMatcher;
34+
import org.springframework.aop.Pointcut;
35+
import org.springframework.aop.support.AopUtils;
36+
import org.springframework.aop.support.DefaultPointcutAdvisor;
37+
import org.springframework.aop.support.Pointcuts;
38+
import org.springframework.aop.support.StaticMethodMatcher;
39+
import org.springframework.beans.factory.annotation.Autowired;
40+
import org.springframework.beans.factory.config.BeanDefinition;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.context.annotation.ImportAware;
44+
import org.springframework.context.annotation.Role;
45+
import org.springframework.core.annotation.AnnotatedElementUtils;
46+
import org.springframework.core.annotation.AnnotationAttributes;
47+
import org.springframework.core.type.AnnotationMetadata;
48+
import org.springframework.security.access.annotation.Jsr250AuthorizationManager;
49+
import org.springframework.security.access.annotation.Secured;
50+
import org.springframework.security.access.annotation.SecuredAuthorizationManager;
51+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
52+
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
53+
import org.springframework.security.access.intercept.aopalliance.AuthorizationMethodInterceptor;
54+
import org.springframework.security.access.method.AuthorizationManagerMethodAfterAdvice;
55+
import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice;
56+
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
57+
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
58+
import org.springframework.security.access.method.DelegatingAuthorizationMethodAfterAdvice;
59+
import org.springframework.security.access.method.DelegatingAuthorizationMethodBeforeAdvice;
60+
import org.springframework.security.access.method.MethodAuthorizationContext;
61+
import org.springframework.security.access.prepost.PostAuthorize;
62+
import org.springframework.security.access.prepost.PreAuthorize;
63+
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
64+
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodAfterAdvice;
65+
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
66+
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodBeforeAdvice;
67+
import org.springframework.security.config.core.GrantedAuthorityDefaults;
68+
69+
/**
70+
* Base {@link Configuration} for enabling Spring Security Method Security.
71+
*
72+
* @author Evgeniy Cheban
73+
* @see EnableMethodSecurity
74+
* @since 5.5
75+
*/
76+
@Configuration(proxyBeanMethods = false)
77+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
78+
final class MethodSecurityConfiguration implements ImportAware {
79+
80+
private MethodSecurityExpressionHandler methodSecurityExpressionHandler;
81+
82+
private GrantedAuthorityDefaults grantedAuthorityDefaults;
83+
84+
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> authorizationMethodBeforeAdvice;
85+
86+
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> authorizationMethodAfterAdvice;
87+
88+
private AnnotationAttributes enableMethodSecurity;
89+
90+
@Bean
91+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
92+
DefaultPointcutAdvisor methodSecurityAdvisor(AuthorizationMethodInterceptor interceptor) {
93+
Pointcut pointcut = Pointcuts.union(getAuthorizationMethodBeforeAdvice(), getAuthorizationMethodAfterAdvice());
94+
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
95+
advisor.setOrder(order());
96+
return advisor;
97+
}
98+
99+
@Bean
100+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
101+
AuthorizationMethodInterceptor authorizationMethodInterceptor() {
102+
return new AuthorizationMethodInterceptor(getAuthorizationMethodBeforeAdvice(),
103+
getAuthorizationMethodAfterAdvice());
104+
}
105+
106+
private MethodSecurityExpressionHandler getMethodSecurityExpressionHandler() {
107+
if (this.methodSecurityExpressionHandler == null) {
108+
this.methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
109+
}
110+
return this.methodSecurityExpressionHandler;
111+
}
112+
113+
@Autowired(required = false)
114+
void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
115+
this.methodSecurityExpressionHandler = methodSecurityExpressionHandler;
116+
}
117+
118+
@Autowired(required = false)
119+
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
120+
this.grantedAuthorityDefaults = grantedAuthorityDefaults;
121+
}
122+
123+
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> getAuthorizationMethodBeforeAdvice() {
124+
if (this.authorizationMethodBeforeAdvice == null) {
125+
this.authorizationMethodBeforeAdvice = createDefaultAuthorizationMethodBeforeAdvice();
126+
}
127+
return this.authorizationMethodBeforeAdvice;
128+
}
129+
130+
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodBeforeAdvice() {
131+
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> beforeAdvices = new ArrayList<>();
132+
beforeAdvices.add(getPreFilterAuthorizationMethodBeforeAdvice());
133+
beforeAdvices.add(getPreAuthorizeAuthorizationMethodBeforeAdvice());
134+
if (securedEnabled()) {
135+
beforeAdvices.add(getSecuredAuthorizationMethodBeforeAdvice());
136+
}
137+
if (jsr250Enabled()) {
138+
beforeAdvices.add(getJsr250AuthorizationMethodBeforeAdvice());
139+
}
140+
return new DelegatingAuthorizationMethodBeforeAdvice(beforeAdvices);
141+
}
142+
143+
private PreFilterAuthorizationMethodBeforeAdvice getPreFilterAuthorizationMethodBeforeAdvice() {
144+
PreFilterAuthorizationMethodBeforeAdvice preFilterBeforeAdvice = new PreFilterAuthorizationMethodBeforeAdvice();
145+
preFilterBeforeAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
146+
return preFilterBeforeAdvice;
147+
}
148+
149+
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> getPreAuthorizeAuthorizationMethodBeforeAdvice() {
150+
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PreAuthorize.class);
151+
PreAuthorizeAuthorizationManager authorizationManager = new PreAuthorizeAuthorizationManager();
152+
authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
153+
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
154+
}
155+
156+
private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> getSecuredAuthorizationMethodBeforeAdvice() {
157+
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(Secured.class);
158+
SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
159+
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
160+
}
161+
162+
private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> getJsr250AuthorizationMethodBeforeAdvice() {
163+
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(DenyAll.class, PermitAll.class,
164+
RolesAllowed.class);
165+
Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager();
166+
if (this.grantedAuthorityDefaults != null) {
167+
authorizationManager.setRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
168+
}
169+
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
170+
}
171+
172+
@Autowired(required = false)
173+
void setAuthorizationMethodBeforeAdvice(
174+
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> authorizationMethodBeforeAdvice) {
175+
this.authorizationMethodBeforeAdvice = authorizationMethodBeforeAdvice;
176+
}
177+
178+
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> getAuthorizationMethodAfterAdvice() {
179+
if (this.authorizationMethodAfterAdvice == null) {
180+
this.authorizationMethodAfterAdvice = createDefaultAuthorizationMethodAfterAdvice();
181+
}
182+
return this.authorizationMethodAfterAdvice;
183+
}
184+
185+
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodAfterAdvice() {
186+
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> afterAdvices = new ArrayList<>();
187+
afterAdvices.add(getPostFilterAuthorizationMethodAfterAdvice());
188+
afterAdvices.add(getPostAuthorizeAuthorizationMethodAfterAdvice());
189+
return new DelegatingAuthorizationMethodAfterAdvice(afterAdvices);
190+
}
191+
192+
private PostFilterAuthorizationMethodAfterAdvice getPostFilterAuthorizationMethodAfterAdvice() {
193+
PostFilterAuthorizationMethodAfterAdvice postFilterAfterAdvice = new PostFilterAuthorizationMethodAfterAdvice();
194+
postFilterAfterAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
195+
return postFilterAfterAdvice;
196+
}
197+
198+
private AuthorizationManagerMethodAfterAdvice<MethodAuthorizationContext> getPostAuthorizeAuthorizationMethodAfterAdvice() {
199+
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PostAuthorize.class);
200+
PostAuthorizeAuthorizationManager authorizationManager = new PostAuthorizeAuthorizationManager();
201+
authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
202+
return new AuthorizationManagerMethodAfterAdvice<>(methodMatcher, authorizationManager);
203+
}
204+
205+
@Autowired(required = false)
206+
void setAuthorizationMethodAfterAdvice(
207+
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> authorizationMethodAfterAdvice) {
208+
this.authorizationMethodAfterAdvice = authorizationMethodAfterAdvice;
209+
}
210+
211+
@Override
212+
public void setImportMetadata(AnnotationMetadata importMetadata) {
213+
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableMethodSecurity.class.getName());
214+
this.enableMethodSecurity = AnnotationAttributes.fromMap(attributes);
215+
}
216+
217+
private boolean securedEnabled() {
218+
return this.enableMethodSecurity.getBoolean("securedEnabled");
219+
}
220+
221+
private boolean jsr250Enabled() {
222+
return this.enableMethodSecurity.getBoolean("jsr250Enabled");
223+
}
224+
225+
private int order() {
226+
return this.enableMethodSecurity.getNumber("order");
227+
}
228+
229+
private static final class SecurityAnnotationsStaticMethodMatcher extends StaticMethodMatcher {
230+
231+
private final Set<Class<? extends Annotation>> annotationClasses;
232+
233+
@SafeVarargs
234+
private SecurityAnnotationsStaticMethodMatcher(Class<? extends Annotation>... annotationClasses) {
235+
this.annotationClasses = new HashSet<>(Arrays.asList(annotationClasses));
236+
}
237+
238+
@Override
239+
public boolean matches(Method method, Class<?> targetClass) {
240+
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
241+
return hasAnnotations(specificMethod) || hasAnnotations(specificMethod.getDeclaringClass());
242+
}
243+
244+
private boolean hasAnnotations(AnnotatedElement annotatedElement) {
245+
Set<Annotation> annotations = AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement,
246+
this.annotationClasses);
247+
return !annotations.isEmpty();
248+
}
249+
250+
}
251+
252+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2021 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.context.annotation.AdviceMode;
23+
import org.springframework.context.annotation.AdviceModeImportSelector;
24+
import org.springframework.context.annotation.AutoProxyRegistrar;
25+
26+
/**
27+
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
28+
* annotation.
29+
*
30+
* @author Evgeniy Cheban
31+
* @since 5.5
32+
*/
33+
final class MethodSecuritySelector extends AdviceModeImportSelector<EnableMethodSecurity> {
34+
35+
@Override
36+
protected String[] selectImports(AdviceMode adviceMode) {
37+
if (adviceMode == AdviceMode.PROXY) {
38+
return getProxyImports();
39+
}
40+
throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
41+
}
42+
43+
private String[] getProxyImports() {
44+
List<String> result = new ArrayList<>();
45+
result.add(AutoProxyRegistrar.class.getName());
46+
result.add(MethodSecurityConfiguration.class.getName());
47+
return result.toArray(new String[0]);
48+
}
49+
50+
}

0 commit comments

Comments
 (0)