Skip to content

Commit db15709

Browse files
rwinchrstoyanchev
authored andcommitted
Add AuthenticationPrincipalArgumentResolver
Closes gh-272
1 parent 5bc9d9a commit db15709

File tree

3 files changed

+562
-0
lines changed

3 files changed

+562
-0
lines changed

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.apache.commons.logging.Log;
3737
import org.apache.commons.logging.LogFactory;
3838
import org.dataloader.DataLoader;
39+
import org.springframework.context.expression.BeanFactoryResolver;
40+
import org.springframework.expression.BeanResolver;
3941
import reactor.core.publisher.Flux;
4042
import reactor.core.publisher.Mono;
4143

@@ -166,6 +168,8 @@ public void afterPropertiesSet() {
166168
this.argumentResolvers.addResolver(new DataLoaderMethodArgumentResolver());
167169
if (springSecurityPresent) {
168170
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
171+
BeanResolver beanResolver = new BeanFactoryResolver(obtainApplicationContext());
172+
this.argumentResolvers.addResolver(new AuthenticationPrincipalArgumentResolver(beanResolver));
169173
}
170174
if (KotlinDetector.isKotlinPresent()) {
171175
this.argumentResolvers.addResolver(new ContinuationHandlerMethodArgumentResolver());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
package org.springframework.graphql.data.method.annotation.support;
17+
18+
import graphql.schema.DataFetchingEnvironment;
19+
import org.reactivestreams.Publisher;
20+
import org.springframework.core.MethodParameter;
21+
import org.springframework.core.ResolvableType;
22+
import org.springframework.core.annotation.AnnotationUtils;
23+
import org.springframework.expression.BeanResolver;
24+
import org.springframework.expression.Expression;
25+
import org.springframework.expression.ExpressionParser;
26+
import org.springframework.expression.spel.standard.SpelExpressionParser;
27+
import org.springframework.expression.spel.support.StandardEvaluationContext;
28+
import org.springframework.graphql.data.method.HandlerMethodArgumentResolver;
29+
import org.springframework.lang.Nullable;
30+
import org.springframework.security.core.Authentication;
31+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
32+
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
33+
import org.springframework.security.core.context.SecurityContext;
34+
import org.springframework.security.core.context.SecurityContextHolder;
35+
import org.springframework.util.Assert;
36+
import org.springframework.util.ClassUtils;
37+
import org.springframework.util.StringUtils;
38+
import reactor.core.publisher.Mono;
39+
40+
import java.lang.annotation.Annotation;
41+
42+
/**
43+
* Resolver to obtain {@link Authentication#getPrincipal()} from Spring Security context via
44+
* {@link SecurityContext#getAuthentication()} for parameters annotated with {@link AuthenticationPrincipal}.
45+
*
46+
* <p>The resolver checks both ThreadLocal context via {@link SecurityContextHolder}
47+
* for Spring MVC applications, and {@link ReactiveSecurityContextHolder} for
48+
* Spring WebFlux applications.
49+
*
50+
* @author Rob Winch
51+
* @since 1.0.0
52+
*/
53+
public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
54+
55+
private ExpressionParser parser = new SpelExpressionParser();
56+
57+
private final BeanResolver beanResolver;
58+
59+
/**
60+
* Creates a new instance.
61+
* @param beanResolver the {@link BeanResolver} used for resolving beans in SpEL expressions. Cannot be null.
62+
*/
63+
public AuthenticationPrincipalArgumentResolver(BeanResolver beanResolver) {
64+
Assert.notNull(beanResolver, "BeanResolver is required");
65+
this.beanResolver = beanResolver;
66+
}
67+
68+
@Override
69+
public boolean supportsParameter(MethodParameter parameter) {
70+
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
71+
}
72+
73+
@Override
74+
public Object resolveArgument(MethodParameter parameter, DataFetchingEnvironment environment) throws Exception {
75+
return getCurrentAuthentication().map(Authentication::getPrincipal)
76+
.flatMap((principal) -> Mono.justOrEmpty(resolvePrincipal(parameter, principal)))
77+
.transform((argument) -> {
78+
boolean isParameterPublisher = isParameterMonoAssignable(parameter);
79+
return isParameterPublisher ? Mono.just(argument) : argument;
80+
});
81+
}
82+
83+
private Mono<Authentication> getCurrentAuthentication() {
84+
SecurityContext securityContext = SecurityContextHolder.getContext();
85+
return Mono.justOrEmpty(securityContext.getAuthentication())
86+
.switchIfEmpty(ReactiveSecurityContextHolder.getContext().map(SecurityContext::getAuthentication));
87+
}
88+
89+
@Nullable
90+
private Object resolvePrincipal(MethodParameter parameter, Object principal) {
91+
AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);
92+
String expressionToParse = annotation.expression();
93+
if (StringUtils.hasLength(expressionToParse)) {
94+
StandardEvaluationContext context = new StandardEvaluationContext();
95+
context.setRootObject(principal);
96+
context.setVariable("this", principal);
97+
context.setBeanResolver(this.beanResolver);
98+
Expression expression = this.parser.parseExpression(expressionToParse);
99+
principal = expression.getValue(context);
100+
}
101+
if (isInvalidType(parameter, principal)) {
102+
if (annotation.errorOnInvalidType()) {
103+
throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());
104+
}
105+
return null;
106+
}
107+
return principal;
108+
}
109+
110+
private boolean isInvalidType(MethodParameter parameter, @Nullable Object principal) {
111+
if (principal == null) {
112+
return false;
113+
}
114+
Class<?> typeToCheck = parameter.getParameterType();
115+
if (isParameterMonoAssignable(parameter)) {
116+
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
117+
Class<?> genericType = resolvableType.resolveGeneric(0);
118+
if (genericType == null) {
119+
return false;
120+
}
121+
typeToCheck = genericType;
122+
}
123+
return !ClassUtils.isAssignable(typeToCheck, principal.getClass());
124+
}
125+
126+
private boolean isParameterMonoAssignable(MethodParameter parameter) {
127+
return Publisher.class.equals(parameter.getParameterType()) ||
128+
Mono.class.equals(parameter.getParameterType());
129+
}
130+
131+
/**
132+
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
133+
* @param annotationClass the class of the {@link Annotation} to find on the
134+
* {@link MethodParameter}
135+
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
136+
* @return the {@link Annotation} that was found or null.
137+
*/
138+
@Nullable
139+
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
140+
T annotation = parameter.getParameterAnnotation(annotationClass);
141+
if (annotation != null) {
142+
return annotation;
143+
}
144+
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
145+
for (Annotation toSearch : annotationsToSearch) {
146+
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
147+
if (annotation != null) {
148+
return annotation;
149+
}
150+
}
151+
return null;
152+
}
153+
}

0 commit comments

Comments
 (0)