javaspring-bootspring-securityjunit5

Authentication is null despite @WithMockUser


I have this controller where it retrieve the user authentication data and print them out.

@GetMapping("/get-role")
public ResponseEntity<String> getRole(Authentication auth) {
    String role = auth.getAuthorities().stream().findFirst().get().toString();
    return ResponseEntity.ok(role);
}

The problem comes when I tried to write the unit test

@Test
@DisplayName("Test Role")
@WithMockUser (username = "user", roles = {"Admin"})
void testRole() throws Exception {
    URI uri = new URI("/v1/me/get-role");
    mockRequest
            .perform(MockMvcRequestBuilders.get(uri))
            .andExpect(MockMvcResultMatchers.status().isOk());
}

The error says:

jakarta.servlet.ServletException: 
    Request processing failed: 
    java.lang.NullPointerException: 
    Cannot invoke "org.springframework.security.core.Authentication.getAuthorities()" because "auth" is null

If you're wondering about my pom.xml, I've included these:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

I've also included these on top of my test class

@WebMvcTest(controllers = MyController.class)
@AutoConfigureMockMvc(addFilters = false)
@ContextConfiguration(classes = SecurityConfig.class)
@Import(MyController.class)
public class MyControllerTest {
// rest of code
}

I've tried many solutions in the internet, such as

User mockUser = Mockito.mock(User.class);
Authentication authentication = Mockito.mock(UsernamePasswordAuthenticationToken.class);

Mockito.when(authentication.getPrincipal()).thenReturn(mockUser);
Mockito.when(authentication.getCredentials()).thenReturn("password");
Mockito.when(authentication.isAuthenticated()).thenReturn(true);
Mockito.when(authentication.getAuthorities()).thenReturn((Collection) List.of(new SimpleGrantedAuthority("Admin")));
mockRequest
     .perform(MockMvcRequestBuilders
         .get(uri)
         .with(SecurityMockMvcRequestPostProcessors
            .user("user")
            .authorities(new SimpleGrantedAuthority("Admin"))))
      .andExpect(MockMvcResultMatchers.status().isOk());

And this solution below gave me different error

mockRequest
     .perform(MockMvcRequestBuilders
         .get(uri)
         .with(SecurityMockMvcRequestPostProcessors.jwt()))
      .andExpect(MockMvcResultMatchers.status().isOk());
java.lang.NoClassDefFoundError: 
    org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter

If you need more information, feel free to add some comment.


Solution

  • TL;DR

    Remove the addFilters = false from the @AutoConfigureMockMvc(addFilters = false) if you want to access authentication

    Spring Security uses filters ...

    When Spring processes a (servlet) request, filters are used to run code before/after the request or to change how it is processed. Spring Security uses a Filter Chain where filters process the request, do checks (blocking the request if applicable), add authentication information or similar. You can configure these with a SecurityFilterChain.

    Typically, there are some filters adding authentication information (for whatever authentication mechanisms you are using) and then there are other filters using that information to check whether the user is authenticated and has the necessary authorities (or similar) according to the configured SecurityFilterChain.

    If you want to see which filters are active (for authentication) and what they are doing, you can enable DEBUG or TRACE logging for Spring Security, make a request and watch the logs.

    ... and you disabled them

    When you configured mockmvc using @AutoConfigureMockMvc, you set addFilters to false. If you take a look at the Javadoc for that, you can see that this means not running the filters:

    boolean addFilters
    If filters from the application context should be registered with MockMVC. Defaults to true.

    Returns:
    if filters should be added

    Default:
    true

    If you disable filters for MockMVC, you won't run Spring Security's filters hence you won't have the authentication information available in the controller.

    What about custom filters

    If you use addFilters = false, request sent using MockMVC won't be sent through any filters, even if you have custom HttpFilters specified in @Components.

    If you don't want Spring to load a filter, just don't let Spring initialize the bean. In your example, you only @Imported MyController. If there are any custom filters, these would normally (unless you told Spring about them in some other way) not be included in your @WebMvcTest if you don't @Import them.