当通过 MockMvc 运行 时,传递给 ControllerAdvice 的身份验证令牌为空
Authentication token passed to ControllerAdvice is null when running through MockMvc
我正在使用 Spring Boot 1.3、Spring 4.2 和 Spring Security 4.0。我正在 运行 使用 MockMvc 进行集成测试,例如:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
在我的测试中,我正在模拟这样的用户登录:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
这适用于我用 @PreAuthorize
注释的方法,例如当从测试中调用这样的方法时:
@PreAuthorize("@permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
原则,我的 CurrentUser 对象,在 PermissionsService#canDoThisThing
中是非空的。
我有一个用 @ControllerAdvice 注释的 class,它将当前登录的用户添加到模型中,以便可以在每个视图中访问它:
@ControllerAdvice
public class CurrentUserControllerAdvice {
@ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
这在 运行 应用程序时工作正常,但是(这是我的问题)- 当 运行 我测试传递给上面的 getCurrentUser 方法的身份验证参数始终为 null。这意味着在我的视图模板中对 currentUser 属性的任何引用都会导致错误,因此这些测试会失败。
我知道我可以通过检索这样的原理来解决这个问题:
authentication = SecurityContextHolder.getContext().getAuthentication();
但我宁愿不更改我的主要代码,以便测试工作。
在 MockMvc
中使用 Spring 安全性时,设置 SecurityContextHolder
不起作用。原因是 Spring 安全的 SecurityContextPersistenceFilter
试图从 HttpServletRequest
解析 SecurityContext
。默认情况下,这是使用 HttpSessionSecurityContextRepository
通过检索名为 SPRING_SECURITY_CONTEXT
的 HttpSession
属性来完成的。属性名称由常量 HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
定义。在 HttpSession
中找到的任何 SecurityContext
都将在 SecurityContextHolder
上设置,这会覆盖您之前设置的值。
手动解决问题
更改最少的修复方法是在 HttpSession
中设置 SecurityContext
。你可以使用这样的东西来做到这一点:
MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
关键是确保将名为 SPRING_SECURITY_CONTEXT
的 HttpSession
属性设置为 SecurityContext
。在我们的示例中,我们利用常量 HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
来定义属性名称。
Spring 安全测试
Spring安全4.0正式加入test support。这是迄今为止使用 Spring 安全性测试您的应用程序的最简单和最灵活的方法。
添加spring-安全测试
由于您使用的是 Spring Boot,确保您拥有此依赖项的最简单方法是在您的 Maven pom.xml 文件中包含 spring-security-test。 Springboot管理版本,所以不需要指定版本
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
自然地,Gradle包含会非常相似。
设置 MockMvc
为了 integrate with MockMvc 您必须执行参考中概述的一些步骤。
第一步是确保您使用 @RunWith(SpringJUnit4ClassRunner.class)
。这应该不足为奇,因为这是使用 Spring 应用程序进行测试时的标准步骤。
next step 是为了确保您使用 SecurityMockMvcConfigurers.springSecurity()
构建 MockMvc
实例。例如:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MyTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
运行 作为用户
现在您可以轻松地 运行 与特定用户。使用 MockMvc 有两种方法可以实现这一点。
使用 RequestPostProcessor
第一个选项是using a RequestPostProcessor。对于您的示例,您可以这样做:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
使用注释
您还可以使用注释来指定用户。由于您使用自定义用户对象(即 CurrentUser),您可能会考虑使用 @WithUserDetails or @WithSecurityContext.
@WithUserDetails 如果您将 UserDetailsService
作为 bean 公开并且您可以接受正在查找的用户(即它必须存在),那么
@WithUserDetails 是有意义的。 @WithUserDetails
的示例可能如下所示:
@Test
@WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
}
替代方法是使用 @WithSecurityContext。如果您不想要求用户实际存在(对于 WithUserDetails 来说是必需的),这是有道理的。我不会对此进行详细说明,因为它有据可查,并且没有关于您的对象模型的更多详细信息,我无法提供具体示例。
我正在使用 Spring Boot 1.3、Spring 4.2 和 Spring Security 4.0。我正在 运行 使用 MockMvc 进行集成测试,例如:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
在我的测试中,我正在模拟这样的用户登录:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
这适用于我用 @PreAuthorize
注释的方法,例如当从测试中调用这样的方法时:
@PreAuthorize("@permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
原则,我的 CurrentUser 对象,在 PermissionsService#canDoThisThing
中是非空的。
我有一个用 @ControllerAdvice 注释的 class,它将当前登录的用户添加到模型中,以便可以在每个视图中访问它:
@ControllerAdvice
public class CurrentUserControllerAdvice {
@ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
这在 运行 应用程序时工作正常,但是(这是我的问题)- 当 运行 我测试传递给上面的 getCurrentUser 方法的身份验证参数始终为 null。这意味着在我的视图模板中对 currentUser 属性的任何引用都会导致错误,因此这些测试会失败。
我知道我可以通过检索这样的原理来解决这个问题:
authentication = SecurityContextHolder.getContext().getAuthentication();
但我宁愿不更改我的主要代码,以便测试工作。
在 MockMvc
中使用 Spring 安全性时,设置 SecurityContextHolder
不起作用。原因是 Spring 安全的 SecurityContextPersistenceFilter
试图从 HttpServletRequest
解析 SecurityContext
。默认情况下,这是使用 HttpSessionSecurityContextRepository
通过检索名为 SPRING_SECURITY_CONTEXT
的 HttpSession
属性来完成的。属性名称由常量 HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
定义。在 HttpSession
中找到的任何 SecurityContext
都将在 SecurityContextHolder
上设置,这会覆盖您之前设置的值。
手动解决问题
更改最少的修复方法是在 HttpSession
中设置 SecurityContext
。你可以使用这样的东西来做到这一点:
MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
关键是确保将名为 SPRING_SECURITY_CONTEXT
的 HttpSession
属性设置为 SecurityContext
。在我们的示例中,我们利用常量 HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY
来定义属性名称。
Spring 安全测试
Spring安全4.0正式加入test support。这是迄今为止使用 Spring 安全性测试您的应用程序的最简单和最灵活的方法。
添加spring-安全测试
由于您使用的是 Spring Boot,确保您拥有此依赖项的最简单方法是在您的 Maven pom.xml 文件中包含 spring-security-test。 Springboot管理版本,所以不需要指定版本
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
自然地,Gradle包含会非常相似。
设置 MockMvc
为了 integrate with MockMvc 您必须执行参考中概述的一些步骤。
第一步是确保您使用 @RunWith(SpringJUnit4ClassRunner.class)
。这应该不足为奇,因为这是使用 Spring 应用程序进行测试时的标准步骤。
next step 是为了确保您使用 SecurityMockMvcConfigurers.springSecurity()
构建 MockMvc
实例。例如:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MyTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
运行 作为用户
现在您可以轻松地 运行 与特定用户。使用 MockMvc 有两种方法可以实现这一点。
使用 RequestPostProcessor
第一个选项是using a RequestPostProcessor。对于您的示例,您可以这样做:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
使用注释
您还可以使用注释来指定用户。由于您使用自定义用户对象(即 CurrentUser),您可能会考虑使用 @WithUserDetails or @WithSecurityContext.
@WithUserDetails 如果您将 UserDetailsService
作为 bean 公开并且您可以接受正在查找的用户(即它必须存在),那么
@WithUserDetails 是有意义的。 @WithUserDetails
的示例可能如下所示:
@Test
@WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
}
替代方法是使用 @WithSecurityContext。如果您不想要求用户实际存在(对于 WithUserDetails 来说是必需的),这是有道理的。我不会对此进行详细说明,因为它有据可查,并且没有关于您的对象模型的更多详细信息,我无法提供具体示例。