Skip to content

Commit

Permalink
Introduce @CurrentSecurityContext for method arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
clevertension committed Feb 27, 2019
1 parent 2b960b0 commit c23a30c
Show file tree
Hide file tree
Showing 7 changed files with 926 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
*/
package org.springframework.security.config.annotation.web.configuration;

import java.util.List;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.BeanResolver;
import org.springframework.security.web.bind.support.CurrentSecurityContextArgumentResolver;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
Expand All @@ -31,6 +30,8 @@
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.support.RequestDataValueProcessor;

import java.util.List;

/**
* Used to add a {@link RequestDataValueProcessor} for Spring MVC and Spring Security CSRF
* integration. This configuration is added whenever {@link EnableWebMvc} is added by
Expand All @@ -52,6 +53,10 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentRes
argumentResolvers.add(authenticationPrincipalResolver);
argumentResolvers
.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());

CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
currentSecurityContextArgumentResolver.setBeanResolver(beanResolver);
argumentResolvers.add(currentSecurityContextArgumentResolver);
argumentResolvers.add(new CsrfTokenArgumentResolver());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.reactive.result.method.annotation.CurrentSecurityContextArgumentResolver;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;

Expand Down Expand Up @@ -78,6 +79,17 @@ public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentRe
return resolver;
}

@Bean
public CurrentSecurityContextArgumentResolver reactiveCurrentSecurityContextArgumentResolver() {
CurrentSecurityContextArgumentResolver resolver = new CurrentSecurityContextArgumentResolver(
this.adapterRegistry);
if (this.beanFactory != null) {
resolver.setBeanResolver(new BeanFactoryResolver(this.beanFactory));
}
return resolver;
}


@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
public ServerHttpSecurity httpSecurity() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that is used to resolve {@link SecurityContext#getAuthentication()} to a method
* argument.
*
* @author Dan Zheng
* @since 5.2.x
*
* See: <a href=
* "{@docRoot}/org/springframework/security/web/bind/support/CurrentSecurityContextArgumentResolver.html"
* > CurrentSecurityContextArgumentResolver </a>
*/
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CurrentSecurityContext {
/**
* True if a {@link ClassCastException} should be thrown when the current
* {@link SecurityContext} is the incorrect type. Default is false.
*
* @return
*/
boolean errorOnInvalidType() default false;
/**
* If specified will use the provided SpEL expression to resolve the security context. This
* is convenient if users need to transform the result.
*
* <p>
* For example, perhaps the user wants to resolve a CustomUser object that is final
* and is leveraging a UserDetailsService. This can be handled by returning an object
* that looks like:
* </p>
*
* <pre>
* public class CustomUserUserDetails extends User {
* // ...
* public CustomUser getCustomUser() {
* return customUser;
* }
* }
* </pre>
*
* Then the user can specify an annotation that looks like:
*
* <pre>
* &#64;CurrentSecurityContext(expression = "authentication")
* </pre>
*
* @return the expression to use.
*/
String expression() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web.bind.support;

import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.core.annotation.CurrentSecurityContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.lang.annotation.Annotation;

/**
* Allows resolving the {@link SecurityContext} using the
* {@link CurrentSecurityContext} annotation. For example, the following
* {@link Controller}:
*
* <pre>
* &#64;Controller
* public class MyController {
* &#64;RequestMapping("/im")
* public void security(@CurrentSecurityContext SecurityContext context) {
* // do something with context
* }
* }
* </pre>
*
* it can also support the spring SPEL expression to get the value from SecurityContext
* <pre>
* &#64;Controller
* public class MyController {
* &#64;RequestMapping("/im")
* public void security(@CurrentSecurityContext(expression="authentication") Authentication authentication) {
* // do something with context
* }
* }
* </pre>
*
* <p>
* Will resolve the SecurityContext argument using {@link SecurityContextHolder#getContext()} from
* the {@link SecurityContextHolder}. If the {@link SecurityContext} is null, it will return null.
* If the types do not match, null will be returned unless
* {@link CurrentSecurityContext#errorOnInvalidType()} is true in which case a
* {@link ClassCastException} will be thrown.
* </p>
*
* @author Dan Zheng
* @since 5.2.x
*/
public final class CurrentSecurityContextArgumentResolver
implements HandlerMethodArgumentResolver {

private ExpressionParser parser = new SpelExpressionParser();

private BeanResolver beanResolver;
/**
* check if this argument resolve can support the parameter.
* @param parameter the method parameter.
* @return true = it can support parameter.
*
* @see
* org.springframework.web.method.support.HandlerMethodArgumentResolver#
* supportsParameter(org.springframework.core.MethodParameter)
*/
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(CurrentSecurityContext.class, parameter) != null;
}

/**
* resolve the argument to inject into the controller parameter.
* @param parameter the method parameter.
* @param mavContainer the model and view container.
* @param webRequest the web request.
* @param binderFactory the web data binder factory.
*
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#
* resolveArgument (org.springframework.core.MethodParameter,
* org.springframework.web.method.support.ModelAndViewContainer,
* org.springframework.web.context.request.NativeWebRequest,
* org.springframework.web.bind.support.WebDataBinderFactory)
*/
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
SecurityContext securityContext = SecurityContextHolder.getContext();
if (securityContext == null) {
return null;
}
Object securityContextResult = securityContext;

CurrentSecurityContext securityContextAnnotation = findMethodAnnotation(
CurrentSecurityContext.class, parameter);

String expressionToParse = securityContextAnnotation.expression();
if (StringUtils.hasLength(expressionToParse)) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(securityContext);
context.setVariable("this", securityContext);

Expression expression = this.parser.parseExpression(expressionToParse);
securityContextResult = expression.getValue(context);
}

if (securityContextResult != null
&& !parameter.getParameterType().isAssignableFrom(securityContextResult.getClass())) {
if (securityContextAnnotation.errorOnInvalidType()) {
throw new ClassCastException(securityContextResult + " is not assignable to "
+ parameter.getParameterType());
}
else {
return null;
}
}
return securityContextResult;
}
/**
* Sets the {@link BeanResolver} to be used on the expressions
* @param beanResolver the {@link BeanResolver} to use
*/
public void setBeanResolver(BeanResolver beanResolver) {
this.beanResolver = beanResolver;
}

/**
* Obtains the specified {@link Annotation} on the specified {@link MethodParameter}.
*
* @param annotationClass the class of the {@link Annotation} to find on the
* {@link MethodParameter}
* @param parameter the {@link MethodParameter} to search for an {@link Annotation}
* @return the {@link Annotation} that was found or null.
*/
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass,
MethodParameter parameter) {
T annotation = parameter.getParameterAnnotation(annotationClass);
if (annotation != null) {
return annotation;
}
Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
for (Annotation toSearch : annotationsToSearch) {
annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(),
annotationClass);
if (annotation != null) {
return annotation;
}
}
return null;
}
}
Loading

0 comments on commit c23a30c

Please sign in to comment.