【问题标题】:Unit Testing rest controller with @AuthenticationPrincipal使用 @AuthenticationPrincipal 对休息控制器进行单元测试
【发布时间】:2022-10-18 14:34:33
【问题描述】:

我正在开发一个带有spring boot和spring security的rest api。

代码如下所示:

@RestController
@RequestMapping(path = "/api")
@PreAuthorize("isAuthenticated()")
public class RestController {

  @GetMapping(path = "/get", produces = "application/json")
  public ResponseEntity<InDto> get(
      @AuthenticationPrincipal final CustomUser user) {

    // ...

    return ResponseEntity.ok(outDto);
  }
}


public class CustomUser {
    // does not inherit from UserDetails
}


public class CustomAuthenticationFilter extends OncePerRequestFilter {

  @Override
  protected void doFilterInternal(
    @NonNull final HttpServletRequest request,
    @NonNull final HttpServletResponse response,
    @NonNull final FilterChain filterChain)
    throws ServletException, IOException {

    if (/* condition */) {
      // ...
      final CustomUser user = new CustomUser(/* parameters */);

      final Authentication authentication =
          new PreAuthenticatedAuthenticationToken(user, "", new ArrayList<>());
      SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    filterChain.doFilter(request, response);
  }
}

我想在没有安全功能的情况下理想地对 RestController 类进行单元测试,但我不知道如何在测试期间注入特定的 CustomUser 对象。

我试图在每次测试之前手动将用户添加到安全上下文中(见下文),但在测试期间注入控制器的用户不是模拟的。

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc(addFilters = false)
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
    SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
    final Authentication auth = new PreAuthenticatedAuthenticationToken(userMock, null, List.of());
    SecurityContextHolder.getContext().setAuthentication(auth);
  }

  @Test
  void test() {
    mockMvc.perform(
            MockMvcRequestBuilders.get("/api/get")
                .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());

  }
}

怎么了?如何将特定的 userMock 注入控制器进行测试?

编辑以使用 @WithMockCustomUser 进行测试

正如文档https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withsecuritycontext 中所建议的,我已将测试更新为:

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
}

@Service
public class WithMockCustomUserSecurityContextFactory
    implements WithSecurityContextFactory<WithMockCustomUser> {

  @Override
  public SecurityContext createSecurityContext(final WithMockCustomUser customUser) {
    final SecurityContext context = SecurityContextHolder.createEmptyContext();

    final Authentication auth =
        new PreAuthenticatedAuthenticationToken(Mockito.mock(IUser.class), null, List.of());
    context.setAuthentication(auth);
    return context;
  }
}

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc(addFilters = false)
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
  }

  @Test
  @WithMockCustomUser
  void test() {
    mockMvc.perform(
            MockMvcRequestBuilders.get("/api/get")
                .contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());

  }
}

但控制器中的用户对象仍然不是模拟(在工厂中创建)

【问题讨论】:

标签: java spring-boot rest spring-security spring-test-mvc


【解决方案1】:

我重写了测试以初始化测试中的安全上下文

@WebMvcTest(RestController.class)
@AutoConfigureMockMvc
@Import(value = {
    CustomAuthenticationFilter.class
})
class RestControllerTest {

  @Autowired private MockMvc mockMvc;
  private CustomerUser userMock;

  @BeforeEach
  public void skipSecurityFilter() {
    userMock = Mockito.mock(CustomUser.class);
  }

  @Test
  void test() {
    PreAuthenticatedAuthenticationToken(userMock, null, List.of());
    SecurityContextHolder.getContext().setAuthentication(auth);
    mockMvc.perform(MockMvcRequestBuilders.get("/api/get").contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers.status().isOk());
  }
}

它有效。

不确定为什么它不适用于@BeforeEach。

【讨论】:

    猜你喜欢
    • 2016-11-14
    • 2017-03-10
    • 2018-07-31
    • 2018-10-17
    • 1970-01-01
    • 2017-04-26
    • 2018-07-31
    • 1970-01-01
    • 2013-05-01
    相关资源
    最近更新 更多