Add BeanResolver to AuthenticationPrincipalArgumentResolver

Previously @AuthenticationPrincipal's expression attribute didn't support
bean references because the BeanResolver was not set on the SpEL context.

This commit adds a BeanResolver and ensures that the configuration
sets a BeanResolver.

Fixes gh-3949
This commit is contained in:
Rob Winch 2016-10-18 19:31:30 -05:00
parent df9e6c973c
commit aaa9708b95
4 changed files with 154 additions and 4 deletions

View File

@ -15,7 +15,14 @@
*/
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.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
@ -24,8 +31,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
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
@ -36,12 +41,15 @@ import java.util.List;
* @author Rob Winch
* @since 3.2
*/
class WebMvcSecurityConfiguration extends WebMvcConfigurerAdapter {
class WebMvcSecurityConfiguration extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private BeanResolver beanResolver;
@Override
@SuppressWarnings("deprecation")
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
authenticationPrincipalResolver.setBeanResolver(beanResolver);
argumentResolvers.add(authenticationPrincipalResolver);
argumentResolvers
.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
argumentResolvers.add(new CsrfTokenArgumentResolver());
@ -51,4 +59,9 @@ class WebMvcSecurityConfiguration extends WebMvcConfigurerAdapter {
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright 2002-2016 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.config.annotation.web.configuration;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
*
* @author Rob Winch
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class AuthenticationPrincipalArgumentResolverTests {
@Autowired
WebApplicationContext wac;
@After
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void authenticationPrincipalExpressionWhenBeanExpressionSuppliedThenBeanUsed() throws Exception {
User user = new User("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()));
SecurityContextHolder.setContext(context);
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.build();
mockMvc.perform(get("/users/self"))
.andExpect(status().isOk())
.andExpect(content().string("extracted-user"));
}
@EnableWebSecurity
@EnableWebMvc
static class Config {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication();
}
@Bean
public UsernameExtractor usernameExtractor() {
return new UsernameExtractor();
}
@RestController
static class UserController {
@GetMapping("/users/self")
public String usersSelf(@AuthenticationPrincipal(expression = "@usernameExtractor.extract(#this)") String userName) {
return userName;
}
}
}
static class UsernameExtractor {
public String extract(User u) {
return "extracted-" + u.getUsername();
}
}
}

View File

@ -6864,6 +6864,26 @@ public ModelAndView findMessagesForUser(@AuthenticationPrincipal(expression = "c
}
----
We can also refer to Beans in our SpEL expressions.
For example, the following could be used if we were using JPA to manage our Users and we wanted to modify and save a propoerty on the current user.
[source,java]
----
import org.springframework.security.core.annotation.AuthenticationPrincipal;
// ...
@PutMapping("/users/self")
public ModelAndView updateName(@AuthenticationPrincipal(expression = "@jpaEntityManager.merge(#this)") CustomUser attachedCustomUser
@RequestParam String firstName) {
// change the firstName on an atached instance which will be persisted to the database
attachedCustomUser.setFirstName(firstName);
// ...
}
----
We can further remove our dependency on Spring Security by making `@AuthenticationPrincipal` a meta annotation on our own annotation. Below we demonstrate how we could do this on an annotation named `@CurrentUser`.
NOTE: It is important to realize that in order to remove the dependency on Spring Security, it is the consuming application that would create `@CurrentUser`. This step is not strictly required, but assists in isolating your dependency to Spring Security to a more central location.

View File

@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
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;
@ -88,6 +89,8 @@ public final class AuthenticationPrincipalArgumentResolver
private ExpressionParser parser = new SpelExpressionParser();
private BeanResolver beanResolver;
/*
* (non-Javadoc)
*
@ -125,6 +128,7 @@ public final class AuthenticationPrincipalArgumentResolver
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(principal);
context.setVariable("this", principal);
context.setBeanResolver(beanResolver);
Expression expression = this.parser.parseExpression(expressionToParse);
principal = expression.getValue(context);
@ -144,6 +148,14 @@ public final class AuthenticationPrincipalArgumentResolver
return principal;
}
/**
* 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}.
*