Skip to content

Commit 678e0b1

Browse files
clevertensionjzheaux
authored andcommitted
Introduce @CurrentSecurityContext for method arguments
1 parent 4a286be commit 678e0b1

File tree

7 files changed

+926
-2
lines changed

7 files changed

+926
-2
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebMvcSecurityConfiguration.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@
1515
*/
1616
package org.springframework.security.config.annotation.web.configuration;
1717

18-
import java.util.List;
19-
2018
import org.springframework.beans.BeansException;
2119
import org.springframework.context.ApplicationContext;
2220
import org.springframework.context.ApplicationContextAware;
2321
import org.springframework.context.annotation.Bean;
2422
import org.springframework.context.expression.BeanFactoryResolver;
2523
import org.springframework.expression.BeanResolver;
24+
import org.springframework.security.web.bind.support.CurrentSecurityContextArgumentResolver;
2625
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
2726
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
2827
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
@@ -31,6 +30,8 @@
3130
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
3231
import org.springframework.web.servlet.support.RequestDataValueProcessor;
3332

33+
import java.util.List;
34+
3435
/**
3536
* Used to add a {@link RequestDataValueProcessor} for Spring MVC and Spring Security CSRF
3637
* integration. This configuration is added whenever {@link EnableWebMvc} is added by
@@ -52,6 +53,10 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
5253
argumentResolvers.add(authenticationPrincipalResolver);
5354
argumentResolvers
5455
.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
56+
57+
CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
58+
currentSecurityContextArgumentResolver.setBeanResolver(beanResolver);
59+
argumentResolvers.add(currentSecurityContextArgumentResolver);
5560
argumentResolvers.add(new CsrfTokenArgumentResolver());
5661
}
5762

config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
3434
import org.springframework.security.crypto.password.PasswordEncoder;
3535
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
36+
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
3637
import org.springframework.web.reactive.config.WebFluxConfigurer;
3738
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
3839

@@ -78,6 +79,17 @@ public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentRe
7879
return resolver;
7980
}
8081

82+
@Bean
83+
public CurrentSecurityContextArgumentResolver reactiveCurrentSecurityContextArgumentResolver() {
84+
CurrentSecurityContextArgumentResolver resolver = new CurrentSecurityContextArgumentResolver(
85+
this.adapterRegistry);
86+
if (this.beanFactory != null) {
87+
resolver.setBeanResolver(new BeanFactoryResolver(this.beanFactory));
88+
}
89+
return resolver;
90+
}
91+
92+
8193
@Bean(HTTPSECURITY_BEAN_NAME)
8294
@Scope("prototype")
8395
public ServerHttpSecurity httpSecurity() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.security.core.annotation;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* Annotation that is used to resolve {@link SecurityContext#getAuthentication()} to a method
26+
* argument.
27+
*
28+
* @author Dan Zheng
29+
* @since 5.2.x
30+
*
31+
* See: <a href=
32+
* "{@docRoot}/org/springframework/security/web/bind/support/CurrentSecurityContextArgumentResolver.html"
33+
* > CurrentSecurityContextArgumentResolver </a>
34+
*/
35+
@Target({ ElementType.PARAMETER })
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Documented
38+
public @interface CurrentSecurityContext {
39+
/**
40+
* True if a {@link ClassCastException} should be thrown when the current
41+
* {@link SecurityContext} is the incorrect type. Default is false.
42+
*
43+
* @return
44+
*/
45+
boolean errorOnInvalidType() default false;
46+
/**
47+
* If specified will use the provided SpEL expression to resolve the security context. This
48+
* is convenient if users need to transform the result.
49+
*
50+
* <p>
51+
* For example, perhaps the user wants to resolve a CustomUser object that is final
52+
* and is leveraging a UserDetailsService. This can be handled by returning an object
53+
* that looks like:
54+
* </p>
55+
*
56+
* <pre>
57+
* public class CustomUserUserDetails extends User {
58+
* // ...
59+
* public CustomUser getCustomUser() {
60+
* return customUser;
61+
* }
62+
* }
63+
* </pre>
64+
*
65+
* Then the user can specify an annotation that looks like:
66+
*
67+
* <pre>
68+
* &#64;CurrentSecurityContext(expression = "authentication")
69+
* </pre>
70+
*
71+
* @return the expression to use.
72+
*/
73+
String expression() default "";
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2002-2013 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+
* http://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+
package org.springframework.security.web.bind.support;
17+
18+
import org.springframework.core.MethodParameter;
19+
import org.springframework.core.annotation.AnnotationUtils;
20+
import org.springframework.expression.BeanResolver;
21+
import org.springframework.expression.Expression;
22+
import org.springframework.expression.ExpressionParser;
23+
import org.springframework.expression.spel.standard.SpelExpressionParser;
24+
import org.springframework.expression.spel.support.StandardEvaluationContext;
25+
import org.springframework.security.core.annotation.CurrentSecurityContext;
26+
import org.springframework.security.core.context.SecurityContext;
27+
import org.springframework.security.core.context.SecurityContextHolder;
28+
import org.springframework.stereotype.Controller;
29+
import org.springframework.util.StringUtils;
30+
import org.springframework.web.bind.support.WebDataBinderFactory;
31+
import org.springframework.web.context.request.NativeWebRequest;
32+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
33+
import org.springframework.web.method.support.ModelAndViewContainer;
34+
35+
import java.lang.annotation.Annotation;
36+
37+
/**
38+
* Allows resolving the {@link SecurityContext} using the
39+
* {@link CurrentSecurityContext} annotation. For example, the following
40+
* {@link Controller}:
41+
*
42+
* <pre>
43+
* &#64;Controller
44+
* public class MyController {
45+
* &#64;RequestMapping("/im")
46+
* public void security(@CurrentSecurityContext SecurityContext context) {
47+
* // do something with context
48+
* }
49+
* }
50+
* </pre>
51+
*
52+
* it can also support the spring SPEL expression to get the value from SecurityContext
53+
* <pre>
54+
* &#64;Controller
55+
* public class MyController {
56+
* &#64;RequestMapping("/im")
57+
* public void security(@CurrentSecurityContext(expression="authentication") Authentication authentication) {
58+
* // do something with context
59+
* }
60+
* }
61+
* </pre>
62+
*
63+
* <p>
64+
* Will resolve the SecurityContext argument using {@link SecurityContextHolder#getContext()} from
65+
* the {@link SecurityContextHolder}. If the {@link SecurityContext} is null, it will return null.
66+
* If the types do not match, null will be returned unless
67+
* {@link CurrentSecurityContext#errorOnInvalidType()} is true in which case a
68+
* {@link ClassCastException} will be thrown.
69+
* </p>
70+
*
71+
* @author Dan Zheng
72+
* @since 5.2.x
73+
*/
74+
public final class CurrentSecurityContextArgumentResolver
75+
implements HandlerMethodArgumentResolver {
76+
77+
private ExpressionParser parser = new SpelExpressionParser();
78+
79+
private BeanResolver beanResolver;
80+
/**
81+
* check if this argument resolve can support the parameter.
82+
* @param parameter the method parameter.
83+
* @return true = it can support parameter.
84+
*
85+
* @see
86+
* org.springframework.web.method.support.HandlerMethodArgumentResolver#
87+
* supportsParameter(org.springframework.core.MethodParameter)
88+
*/
89+
public boolean supportsParameter(MethodParameter parameter) {
90+
return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
91+
}
92+
93+
/**
94+
* resolve the argument to inject into the controller parameter.
95+
* @param parameter the method parameter.
96+
* @param mavContainer the model and view container.
97+
* @param webRequest the web request.
98+
* @param binderFactory the web data binder factory.
99+
*
100+
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#
101+
* resolveArgument (org.springframework.core.MethodParameter,
102+
* org.springframework.web.method.support.ModelAndViewContainer,
103+
* org.springframework.web.context.request.NativeWebRequest,
104+
* org.springframework.web.bind.support.WebDataBinderFactory)
105+
*/
106+
public Object resolveArgument(MethodParameter parameter,
107+
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
108+
WebDataBinderFactory binderFactory) throws Exception {
109+
SecurityContext securityContext = SecurityContextHolder.getContext();
110+
if (securityContext == null) {
111+
return null;
112+
}
113+
Object securityContextResult = securityContext;
114+
115+
CurrentSecurityContext securityContextAnnotation = findMethodAnnotation(
116+
CurrentSecurityContext.class, parameter);
117+
118+
String expressionToParse = securityContextAnnotation.expression();
119+
if (StringUtils.hasLength(expressionToParse)) {
120+
StandardEvaluationContext context = new StandardEvaluationContext();
121+
context.setRootObject(securityContext);
122+
context.setVariable("this", securityContext);
123+
124+
Expression expression = this.parser.parseExpression(expressionToParse);
125+
securityContextResult = expression.getValue(context);
126+
}
127+
128+
if (securityContextResult != null
129+
&& !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
130+
if (securityContextAnnotation.errorOnInvalidType()) {
131+
throw new ClassCastException(securityContextResult + " is not assignable to "
132+
+ parameter.getParameterType());
133+
}
134+
else {
135+
return null;
136+
}
137+
}
138+
return securityContextResult;
139+
}
140+
/**
141+
* Sets the {@link BeanResolver} to be used on the expressions
142+
* @param beanResolver the {@link BeanResolver} to use
143+
*/
144+
public void setBeanResolver(BeanResolver beanResolver) {
145+
this.beanResolver = beanResolver;
146+
}
147+
148+
/**
149+
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
150+
*
151+
* @param annotationClass the class of the {@link Annotation} to find on the
152+
* {@link MethodParameter}
153+
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
154+
* @return the {@link Annotation} that was found or null.
155+
*/
156+
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass,
157+
MethodParameter parameter) {
158+
T annotation = parameter.getParameterAnnotation(annotationClass);
159+
if (annotation != null) {
160+
return annotation;
161+
}
162+
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
163+
for (Annotation toSearch : annotationsToSearch) {
164+
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(),
165+
annotationClass);
166+
if (annotation != null) {
167+
return annotation;
168+
}
169+
}
170+
return null;
171+
}
172+
}

0 commit comments

Comments
 (0)