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.
Remove the addFilters = false
from the @AutoConfigureMockMvc(addFilters = false)
if you want to access authentication
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.
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 addedDefault:
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.
If you use addFilters = false
, request sent using MockMVC won't be sent through any filters, even if you have custom HttpFilter
s specified in @Component
s.
If you don't want Spring to load a filter, just don't let Spring initialize the bean. In your example, you only @Import
ed 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.