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

Pet*_*zov 2 java junit spring spring-security spring-boot

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

    @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(""));
    }
Run Code Online (Sandbox Code Playgroud)

GitHub

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

{
  "resetPasswordToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.VFb0qJ1LRg_4ujbZoRMXnVkUgiuKq5KxWqNdbKq_G9Vvz-S1zZa9LPxtHWKa64zDl2ofkT8F6jBt_K4riU-fPg",
  "login": "login",
  "status": "1",
  "error": "",
  "errorDescription": ""
}
Run Code Online (Sandbox Code Playgroud)

我得到了这条线的 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)
Run Code Online (Sandbox Code Playgroud)

完整日志

我得到此代码的 NPE:

return (Users) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Run Code Online (Sandbox Code Playgroud)

GitHub

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

Pat*_*mil 6

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

\n\n
\n\n

@WithMockUser

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

@WithAnonymousUser

\n\n

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

\n\n
\n\n

@WithUserDetails

\n\n

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

\n\n

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

\n\n
\n\n

@WithSecurityContext

\n\n

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

\n