Spring Boot:使用 Junit 模拟具有安全上下文的用户身份验证

Posted

技术标签:

【中文标题】Spring Boot:使用 Junit 模拟具有安全上下文的用户身份验证【英文标题】:Spring Boot : Mock user authentication with security context using Junit 【发布时间】:2020-10-10 05:01:03 【问题描述】:

我想为使用 JWT 令牌验证的 Rest API 创建 JUnit 5 测试:

    @Test
    public void resetTokenTest_OK() throws Exception 
        when(userService.findByResetPasswordToken(anyString())).thenReturn(trueOptional);

        mockMvc.perform(post("/users/reset_token")
                .contentType(MediaType.APPLICATION_JSON)
                .content(ResetPasswordTokenDTO))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.resetPasswordToken").value(randomString_254))
                .andExpect(jsonPath("$.login").value("login"))
                .andExpect(jsonPath("$.status").value("200"))
                .andExpect(jsonPath("$.error").value(""))
                .andExpect(jsonPath("$.errorDescription").value(""));
    

GitHub

我使用此有效负载发出 Mockup 请求:


  "resetPasswordToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.VFb0qJ1LRg_4ujbZoRMXnVkUgiuKq5KxWqNdbKq_G9Vvz-S1zZa9LPxtHWKa64zDl2ofkT8F6jBt_K4riU-fPg",
  "login": "login",
  "status": "1",
  "error": "",
  "errorDescription": ""

我得到了这个line的NPE:

Caused by: java.lang.NullPointerException
    at org.engine.utils.GenericUtils.getLoggedInUser(GenericUtils.java:15)
    at org.engine.rest.UsersController.lambda$resetToken$0(UsersController.java:159)
    at java.base/java.util.Optional.map(Optional.java:258)
    at org.engine.rest.UsersController.resetToken(UsersController.java:153)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)

完整的Log:

我得到了这段代码的 NPE:

return (Users) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

GitHub

我如何模拟来自SecurityContextHolder.getContext().getAuthentication().getPrincipal() 的结果以免获得 NPE?

【问题讨论】:

不要嘲笑它;请改用 Spring Security 测试支持(例如,@WithUser)。 我需要把它放在哪里? 试着把它放在失败的测试之上。 【参考方案1】:

有多种方法可以使用@WithMockUser@WithAnonymousUser@WithUserDetails@WithSecurityContext 来模拟安全性。您可以将这些注释与@Test 方法一起使用


@WithMockUser

用户名“user”的用户不必存在,因为我们正在模拟用户 SecurityContext 中填充的身份验证是 UsernamePasswordAuthenticationToken 类型 Authentication 的主体是 Spring Security 的 User 对象 用户将拥有 "user" 的用户名,密码 "password",以及名为 "ROLE_USER" 的单个 GrantedAuthority用过。 您可以自定义用户名、角色、值等参数

@WithAnonymousUser

使用@WithAnonymousUser 允许以匿名用户身份运行。当您希望以特定用户运行大部分测试,但又想以匿名用户身份运行一些测试时,这尤其方便。例如,以下将使用 @WithMockUser以匿名用户身份匿名运行 withMockUser1 和 withMockUser2。


@WithUserDetails

虽然@WithMockUser 是一种非常方便的入门方式,但它可能并非在所有情况下都有效。例如,应用程序通常期望Authentication 主体属于特定类型。这样做是为了让应用程序可以将主体引用为自定义类型并减少对 Spring Security 的耦合。

自定义主体通常由自定义UserDetailsService 返回,该自定义主体返回实现UserDetails 和自定义类型的对象。对于这种情况,使用自定义 UserDetailsS​​ervice 创建测试用户很有用。这正是@WithUserDetails 所做的。


@WithSecurityContext

我们可以创建自己的注解,使用@WithSecurityContext 来创建我们想要的任何SecurityContext。例如,我们可以创建一个名为@WithMockCustomUser 的注解为@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)

【讨论】:

以上是关于Spring Boot:使用 Junit 模拟具有安全上下文的用户身份验证的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JUnit 在 Spring Boot 中的公共方法中模拟私有方法

在 JUnit 5 测试中模拟 Spring Boot 2 应用程序的自动装配依赖项

@MockBean 不适用于带有 JUnit 5 和 Spring Boot 2 的 @WebMvcTest?

使用 JUnit 5 的 spring-boot-starter-test

Spring-boot,使用不同配置文件的 JUnit 测试

spring-boot2.0 单元测试JUnit4