为什么我在 `@WebMvcTest` 中得到 null `Authentication` 作为 @Controller 方法参数?

Why do I get null `Authentication` as @Controller method parameter in `@WebMvcTest`?

上下文:我创建了一个测试注释 @WithMockAuthentication 以使用 Authentication 实例填充测试安全上下文,就像 @WithMockUser 所做的那样。 主要区别在于,在我的例子中,实例是 Mockito 模拟。

我的经验:一旦我用模拟替换实际实例,作为控制器方法参数提供的 Authentication 实例在带注释的测试中为 null:在 WithSecurityContextFactory 中,如果我替换:

    public Authentication workingAuthentication(WithMockAuthentication annotation) {
        return new TestAuthentication(annotation.name(), Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
    }

    public Authentication bogousAuthentication(WithMockAuthentication annotation) {
        var auth = mock(Authentication.class);
        when(auth.getName()).thenReturn(annotation.name());
        when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
        when(auth.isAuthenticated()).thenReturn(true);
        return auth;
    }

然后我在

的控制器测试中得到 NPE
    @RequestMapping("/method")
    @PreAuthorize("hasRole('ROLE_AUTHORIZED')")
    public ResponseEntity<String> securedMethod(Authentication auth) {
        // Here, auth is null if Authentication is a mock
        return ResponseEntity.ok(String.format("Hey %s, how are you?", auth.getName()));
    }

我创建了一个 minimal sample to reproduce。 运行测试失败。

我很确定我遇到了错误。足够创建an issue in spring-security project,但似乎Spring团队团队没有时间调查...

[编辑] 这最后的陈述是无用的冒犯并且完全错误,因为答案是由 Rob Winch 提供的,他是 spring-security 的主要成员:/ My Bad

您的 SampleController is of type Authentication which is an instance of Principal and thus the ServletRequestMethodArgumentResolver 的论点将尝试解决 HttpServletRequest.getUserPrincipal() 的论点。

mock that you are creating 没有存根 Authentication.getPrincipal() 方法。

public Authentication bogousAuthentication(WithMockAuthentication annotation) {
    var auth = mock(Authentication.class);
    when(auth.getName()).thenReturn(annotation.name());
    when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
    when(auth.isAuthenticated()).thenReturn(true);
    return auth;
}

因此,Authentication.getPrincipal()null,因此 SecurityContextHolderAwareRequestWrapper.getUserPrincipal() returns null. Why does it return null when the principal is null? I cannot be certain the original intention as the code was added before I was a team member. However, it makes sense in Spring Security's model. Authentication can represent both an authenticated user and credentials used for authenticating. The Javadoc of Authentication.getPrincipal() 声明(强调我的):

The identity of the principal being authenticated. In the case of an authentication request with username and password, this would be the username. Callers are expected to populate the principal for an authentication request.

The AuthenticationManager implementation will often return an Authentication containing richer information as the principal for use by the application. Many of the authentication providers will create a UserDetails object as the principal.

null 检查是为了确保 Authentication 确实代表经过身份验证的用户。

要修复它,您必须用 when(auth.getPrincipal()).thenReturn("bogus"); 之类的东西删除 getPrincipal() 方法。更改可以在下面和我的 pull request.

中看到
public Authentication bogousAuthentication(WithMockAuthentication annotation) {
    var auth = mock(Authentication.class);
    when(auth.getPrincipal()).thenReturn("bogus");
    when(auth.getName()).thenReturn(annotation.name());
    when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
    when(auth.isAuthenticated()).thenReturn(true);
    return auth;
}