spring-security 使用@AuthenticationPrincipal对其余控制器进行单元测试

rqdpfwrv  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(210)

我正在开发一个带有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进行测试
如文档www.example.com中所建议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());

  }
}

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

hm2xizp9

hm2xizp91#

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

@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一起工作。

相关问题