本文介绍了通过MockMvc运行时,传递给ControllerAdvice的身份验证令牌为null的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用Spring Boot 1.3,Spring 4.2和Spring Security 4.0.我正在使用MockMvc运行集成测试,例如:

I am using Spring Boot 1.3, Spring 4.2 and Spring Security 4.0. I am running integration tests using MockMvc, for example:

mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
            .andExpect(status().isOk())
            .etc;

在测试中,我正在模拟这样的用户登录:

In my tests I am simulating a user login like this:

CurrentUser principal = new CurrentUser(user);
Authentication auth =
                new UsernamePasswordAuthenticationToken(principal, "dummypassword",
                    principal.getAuthorities());

SecurityContextHolder.getContext().setAuthentication(auth);

这对于用@PreAuthorize注释的我的方法很好用,例如,当从测试中调用这样的方法时:

This works fine for my methods that are annotated with @PreAuthorize, for example when calling a method like this from a test:

@PreAuthorize("@permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()

该原理(我的CurrentUser对象)在PermissionsService#canDoThisThing中不为空.

the principle, my CurrentUser object, is non-null in PermissionsService#canDoThisThing.

我有一个用@ControllerAdvice注释的类,该类将当前登录的用户添加到模型中,以便可以在每个视图中对其进行访问:

I have a class annotated with @ControllerAdvice that adds the currently logged-in user to the model so it can be accessed in every view:

@ControllerAdvice
public class CurrentUserControllerAdvice {
    @ModelAttribute("currentUser")
    public CurrentUser getCurrentUser(Authentication authentication) {
        if (authentication == null) {
            return null;
        }

        return (CurrentUser) authentication.getPrincipal();
    }
}

这在运行应用程序时运行良好,但是(这是我的问题)-在运行测试时,传递给上述getCurrentUser方法的身份验证参数始终为null.这意味着在我的视图模板中对currentUser属性的任何引用都会导致错误,因此这些测试将失败.

This works fine when running the application, however (and this is my problem) - when running my tests the authentication parameter passed in to the getCurrentUser method above is always null. This means any references to the currentUser attribute in my view templates cause errors, so those tests fail.

我知道我可以通过检索这样的原理来解决这个问题:

I know I could get round this by retrieving the principle like this:

authentication = SecurityContextHolder.getContext().getAuthentication();

但是我宁愿不更改主代码,只是为了使测试正常工作.

but I would rather not change my main code just so the tests work.

推荐答案

在将Spring Security与MockMvc一起使用时,设置SecurityContextHolder不起作用.原因是Spring Security的SecurityContextPersistenceFilter尝试从HttpServletRequest解析SecurityContext.默认情况下,这是通过使用HttpSessionSecurityContextRepository通过获取名为SPRING_SECURITY_CONTEXTHttpSession属性来完成的.属性名称由常量HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY定义.在HttpSession中找到的任何SecurityContext都将在SecurityContextHolder上设置,它将覆盖您先前设置的值.

Setting the SecurityContextHolder does not work when using Spring Security with MockMvc. The reason is that Spring Security's SecurityContextPersistenceFilter attempts to resolve the SecurityContext from the HttpServletRequest. By default this is done using HttpSessionSecurityContextRepository by retrieving at the HttpSession attribute named SPRING_SECURITY_CONTEXT. The attribute name is defined by the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY. Whatever SecurityContext is found in the HttpSession will then be set on the SecurityContextHolder which overrides the value you previously set.

涉及最少更改的解决方法是在HttpSession中设置SecurityContext.您可以使用以下方式进行此操作:

The fix that involves the least amount of change is to set the SecurityContext in the HttpSession. You can do this using something like this:

MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
            .andExpect(status().isOk())
            .etc;

关键是确保将名为SPRING_SECURITY_CONTEXTHttpSession属性设置为SecurityContext.在我们的示例中,我们利用常量HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY定义属性名称.

The key is to ensure that you set the HttpSession attribute named SPRING_SECURITY_CONTEXT to the SecurityContext. In our example, we leverage the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY to define the attribute name.

Spring Security 4.0已正式添加测试支持.到目前为止,这是使用Spring Security测试应用程序的最简单,最灵活的方法.

Spring Security 4.0 has officially added test support. This is by far the easiest and most flexible way to test your application with Spring Security.

由于您使用的是Spring Boot,因此确保具有这种依赖性的最简单方法是在Maven pom中包含spring-security-test. Spring Boot管理版本,因此无需指定版本.

Since you are using Spring Boot, the easiest way to ensure you have this dependency is to include spring-security-test in your Maven pom. Spring Boot manages the version, so there is no need to specify a version.

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

自然地,Gradle包含将非常相似.

Naturally, Gradle inclusion would be very similar.

为了与MockMvc 在参考中必须概述一些步骤.

In order to integrate with MockMvc there are some steps you must perform outlined in the reference.

第一步是确保您使用@RunWith(SpringJUnit4ClassRunner.class).这并不奇怪,因为这是在使用Spring应用程序进行测试时的标准步骤.

The first step is to ensure you use @RunWith(SpringJUnit4ClassRunner.class). This should come as no surprise since this is a standard step when testing with Spring applications.

下一步是为了确保您使用SecurityMockMvcConfigurers.springSecurity()构建MockMvc实例.例如:

The next step is to ensure you build your MockMvc instance using SecurityMockMvcConfigurers.springSecurity(). For example:

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有两种方法可以实现这一点.

Running as a User

Now you can easily run with a specific user. There are two ways of accomplishing this with MockMvc.

第一个选项是使用RequestPostProcessor .对于您的示例,您可以执行以下操作:

The first option is using a RequestPostProcessor. For your example, you could do something like this:

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 @ WithSecurityContext .

@WithUserDetails 如果将UserDetailsService公开为bean,并且可以很好地查找用户(即它必须存在),则很有意义. @WithUserDetails的示例可能类似于:

@WithUserDetails makes sense if you expose the UserDetailsService as a bean and you are alright with the user being looked up (i.e. it must exist). An example of @WithUserDetails might look like:

@Test
@WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
    MvcResult result = mockMvc.perform(get("/"))
        .andExpect(status().isOk())
        .etc;
}

替代方法是使用 @WithSecurityContext .如果您不想要求用户实际存在(这对于WithUserDetails是必需的),则这很有意义.我不会对此进行详细说明,因为它已被很好地记录下来,并且没有关于您的对象模型的更多细节,因此我无法提供具体的示例.

The alternative is to use @WithSecurityContext. This makes sense if you do not want to require the user to actually exist (as is necessary for WithUserDetails). I won't elaborate on this as it is well documented and without more details about your object model, I cannot provide a concrete example of this.

这篇关于通过MockMvc运行时,传递给ControllerAdvice的身份验证令牌为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-20 05:41