Spring Boot WebMvcTest:如何使用Authentication对象参数测试控制器方法



这是这个问题的延续Spring WebMvcTest如何模拟身份验证?

我试图在Spring引导中测试一个控制器方法,该方法接收一个Authentication对象作为参数。控制器是带有@CrossOrigin注释的RestController。方法如下:

@GetMapping("/authentication")
public String testAuthentication(Authentication authentication) {
UserDetailsStub userDetailsStub = (UserDetailsStub) authentication.getPrincipal();
return userDetailsStub.getUsername();
}

正如您所看到的,我从参数中的身份验证中获取主体。

问题是,在我的WebMvcTest测试用例中,我得到了一个NullPointerException,因为在测试用例中authentication似乎为null。我的问题是为什么?

我尝试添加一个given调用,它将在测试用例中的@PostConstruct注释方法中返回一个自定义UserDetails对象,但我仍然得到了NullPointerException

我的测试用例如下:

@Import(SecurityConfiguration.class)
@RunWith(SpringRunner.class)
@WebMvcTest(PDPController.class)
@AutoConfigureMockMvc(addFilters = false)
public class PDPControllerTests {
@Autowired
private MockMvc mvc;
@MockBean(name = "userDetailsService")
private MyUserDetailsService userDetailsService;

//..
@PostConstruct
public void setup() {
given(userDetailsService.loadUserByUsername(anyString()))
.willReturn(new UserDetailsStub());
}

//..
@Test
@WithUserDetails(value = "username", userDetailsServiceBeanName = "userDetailsService")
public void testAuthentication() throws Exception {
mvc.perform(get("/pdps/authentication").secure(true)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}

}

为什么authentication在测试用例中为null,即使我在@PostConstruct方法中提供了它?

这里可以找到一个GitHub项目,该项目的代码最少,可以重现错误。https://github.com/Kars1090/SpringSecurityTest

谢谢!

克隆您的项目后,我已经在您的控制器方法中接收到一个有效的Authentication对象。基本上你在测试中有两个主要问题:

  1. 不必要的额外配置
  2. 过滤器的错误模拟配置:JwtRequestFilter

总之,变化如下:

public class UserDetailsStub implements UserDetails {
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;
public UserDetailsStub() {}

public static UserDetailsStub of (User user) {
UserDetailsStub userDetails = new UserDetailsStub();
if (null != user) {
userDetails.username = user.getUsername();
userDetails.password = user.getPassword();
userDetails.authorities = user.getAuthorities();
}
return userDetails;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
// Rest of the code is equal to your version

您的控制器方法:

@GetMapping("/authentication")
public String testAuthentication(Authentication authentication) {
UserDetailsStub userDetailsStub = UserDetailsStub.of((User) 
authentication.getPrincipal());
return userDetailsStub.getUsername();
}

测试:

@WebMvcTest(value = PDPController.class)
public class PDPControllerTests {
@Autowired
private MockMvc mvc;
/** You have not to mock the filter because in that case Spring
* won't know how to deal with it, when the list of them
* should be managed.
*
* That is the reason why you had to include
* @AutoConfigureMockMvc(addFilters = false), but that
* is preciselly what was avoiding the creation of your
* Authentication object, because your JwtRequestFilter
* was not being executed.
*
* With the current code, your filter will be executed and
* the Authentication object created.
*/
//@MockBean
//private JwtRequestFilter jwtRequestFilter;
// What you have to mock are the classes the filter uses internally
@MockBean
private MyUserDetailsService userDetailsService;
@MockBean
private JwtService jwtService;
@Test
@WithMockUser
public void test() throws Exception {
mvc.perform(
get("/pdps/authentication").secure(true)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}

一个(坏的(解决方法是从控制器参数中删除authentication,而通过SecurityContextHolder.getContext().getAuthentication()进行身份验证。

这将使测试在不改变任何其他内容的情况下工作。

最新更新