Spring Boot 2, Spring Security 5 and #WithMockUser - spring

Since I migrated to Spring Boot 2.0.5 from 1.x, with no mean to disable security, I can't get test roles to work on mock MVC tests :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class ApplicationsControllerShould {
...
#Autowired
private MockMvc mockMvc;
private ObjectMapper mapper = new ObjectMapper();
#Test
#WithMockUser(roles = "ADMIN")
public void handle_CRUD_for_applications() throws Exception {
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // failure 403!
...
My controller endpoint isn't even protected!
#RestController
#RequestMapping("/applications")
public class ApplicationsController {
...
#PostMapping
public Application addApplication(#RequestBody Application application) {
Assert.isTrue(!applicationsDao.existsById(application.getCode()), "Application code already exists: " + application.getCode());
return applicationsDao.save(application);
}
}
So I have in the test a session (#authenticated fails when #WithMockUser is commented out) and a role by the way (ROLE_ADMIN is visible in traces) but my request is being rejected and I don't understand what I did wrong.
Thx for any idea!

Ok... the good old CSRF stuff, then...
logging.level.org.springframework.security=DEBUG
2018-10-02 10:11:41.285 DEBUG 12992 --- [ main] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost/applications/foo
Application app = Application.builder()
.code(APP_CODE).name(APP_NAME)
.build();
mockMvc.perform(post("/applications").with(csrf()) // oups...
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(mapper.writeValueAsString(app)))
.andExpect(authenticated())
.andExpect(status().isOk()); // there we go!

Related

Mocking OAuth2 client with WebTestClient for servlet applications results in null httpHandlerBuilder

My Spring Boot application acts as an OAuth2 client by using the spring-boot-starter-oauth2-client dependency.
Now I'd like to write an integration test (#SpringBootTest) to verify the behavior of a REST endpoint secured by OAuth2. The Testing OAuth 2.0 Clients documentation describes that it is possible to use mutateWith(mockOAuth2Client()) to mock a login via OAuth2.
public class UserIT {
#Autowired
private WebTestClient webTestClient;
#Test
void test() {
webTestClient
.mutateWith(mockOAuth2Client("keycloak"))
.get()
.uri("/api/user/1345")
.exchange()
.expectStatus().isOk();
}
}
However, the test fails with the following message:
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filters(java.util.function.Consumer)" because "httpHandlerBuilder" is null
at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$OAuth2ClientMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:1113)
at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.apply(DefaultWebTestClientBuilder.java:265)
at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:167)
As far as I have understood it, this WebTestClient setup is only suitable for "Reactive Applications" whereas my application is a "Servlet Application". Unfortunately, I cannot find the necessary information how to mock this OAuth2 client for a servlet application.
I was able to run your exact #Autowired and #Test code successfully with the following test configuration:
#Configuration
#ComponentScan
public class TestConfig {
#Bean
public WebTestClient webTestClient(ApplicationContext applicationContext) {
return WebTestClient.bindToApplicationContext(applicationContext).build();
}
#Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange().anyExchange().permitAll();
return http.build();
}
}

Spring Boot MockMvc Authentication Object Is Null

#DeleteMapping("/uri/{id}")
public ResponseEntity<RestResponseBody> delete(#PathVariable Long id, Authentication auth) {
return ResponseEntity.ok(new RestResponseBody());
}
This is the controller, and the Authentication object is null when I use MockMvc. But, SecurityContextHolder.getContext().getAuthentication() is not null. I can get the authentication information from SecurityContextHolder, but not from the Authentication parameter.
Note: While not using the MockMvc, there is no problem.
#SpringBootTest(classes = TestApplication.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
#AutoConfigureMockMvc(addFilters = false)
public class XTest {
#Autowired
private MockMvc mockMvc;
#Test
#Transactional
#WithMyUser
void deleteTest() throws Exception {
MyUser principal = (MyUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.delete("/uri/{id}", 00000)
.with(authentication(SecurityContextHolder.getContext().getAuthentication()))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)).andReturn();
System.out.println(result);
}
}
The principal in the test class is not null. But when I send the request to the controller, the authentication parameter becomes null even if SecurityContextHolder is not null. What I am missing here?
https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/test-mockmvc.html#test-mockmvc-setup
In order to use Spring Security with Spring MVC Test it is necessary to add the Spring Security FilterChainProxy as a Filter. It is also necessary to add Spring Security’s TestSecurityContextHolderPostProcessor to support Running as a User in Spring MVC Test with Annotations. This can be done using Spring Security’s SecurityMockMvcConfigurers.springSecurity()

mock rest api call during integration test in spring boot

I have a spring boot service that validates each request by calling my auth service. Now I am writing an integration test. How can I mock my request to auth service while testing my own APIs?
#GetMapping("/pending/task")
#Operation(summary = "Get user's pending task", tags = "UserTask", security = {#SecurityRequirement(name = Constants.AUTH_TOKEN_HEADER)})
#PreAuthorize(Constants.PreAuthorize.ROLE)
public List<UserTaskDto> getPendingTasks(#Valid #RequestParam long courseId){
// internal logic
}
SpringBoot filter will read the token from the header and verify that against auth service using rest. I want to mock that call during this api testing.
Test Code
class UserTaskControllerTest extends ApplicationTests {
#Mock
RestTemplate restTemplate;
#Test
void shouldGiveAllUserPendingTask(){
HttpHeaders headers = new HttpHeaders();
headers.add(Constants.AUTH_TOKEN_HEADER, GENERIC_AUTH_TOKEN);
Task task = FactoryClass.createTask();
UserTask userTask = FactoryClass.createUserTask();
CentralAuthInfo centralAuthInfo = FactoryClass.getCentralAuthInfo();
taskRepository.save(task);
userTask.setTask(task);
userTaskRepository.save(userTask);
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(USER_PENDING_TASK_BASE_URL)
.queryParam(COURSE_ID, userTask.getCohortId());
when(restTemplate.exchange(ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class), ArgumentMatchers.any(HttpEntity.class), ArgumentMatchers.eq(CentralAuthInfo.class))).thenReturn(new ResponseEntity<>(centralAuthInfo, HttpStatus.OK));
ResponseEntity<UserTaskDto> responseEntity = testRestTemplate.exchange(builder.toUriString(), GET, new HttpEntity<>(headers), UserTaskDto.class);
assertThat(responseEntity.getStatusCode()).isEqualTo(200);
}
Spring offers a #WithMockUser that you can add to your tests. I usually use it with a WebTextClient to test API calls. Here is an example with reactive controller but same applies to non-reactive
#Import(SecurityConfig.class)
#WebFluxTest(MyController.class)
class MyControllerTest {
#Autowired
private WebTestClient webTestClient;
...
#Test
#WithMockUser(username="admin",roles={"USER","ADMIN", "ROLE"})
void testPendingTasks() {
webTestClient
.get()
.uri("/pending/task")
.exchange()
.expectStatus()
.isOk();
}
...
}
Maybe #AutoConfigureMockMvc(addFilters = false) will help to disable security for integration test, like this:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ExtendWith(SpringExtension.class)
#AutoConfigureMockMvc(addFilters = false)
public class FooTest {
#Autowired
private MockMvc mockMvc;
// some of your tests here ....
}
If it's not what you need you can just create a fake auth service class for test purposes and override the behavior in the way you want.

#WebMvcTest for SOAP?

Is the Spring Boot annotation #WebMvcTest only intended for sliced RestController tests or should SOAP Endpoints be testable with it too?
When I setup my test and run it, I only get a 404 response as if the endpoint wasn't there so I assume it isn't part of the WebMvc slice.
#RunWith(SpringRunner.class)
#WebMvcTest(value = IdServerPortTypeV10.class)
#Import({SecurityConfig.class, ModelMapperConfig.class, WebServiceConfig.class, ControllerTestBeans.class})
public class AccountEndpointTests {
#Autowired
IdServerPortTypeV10 soapEndpoint;
...
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(wac)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(roles = VALID_ROLE)
public void getAccountTest_Success() throws Exception {
mockMvc.perform(
post("/soap/idserver/1.0")
.accept(MediaType.TEXT_XML_VALUE)
.headers(SoapTestUtility.getHeader(SERVICE.getNamespaceURI(), "getAccount"))
.content(SoapTestUtility.getAccountXml())
).andDo(print())
.andExpect(status().isOk());
}
}
The endpoint is enabled in WebServiceConfig.class in which #EnableWs is set.
#WebMvcTest is, as the name implies, only for Spring MVC related tests.
Spring's SOAP support is from the Spring Web Services project which has its own integration testing support.

Mock SecurityContextHolder / Authentication always returning null

I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using #WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.
Class
#Override
public ResponseEntity<EmployeeDTO> meGet() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
logger.debug("Endpoint called: me({})", principal);
EmployeeDTO result;
// Get user email from security context
String email = principal.getName(); // NPE here
// ...
}
Test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"eureka.client.enabled:false"})
#WithMockUser
#ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {
#Autowired
private TestRestTemplate restTemplate;
#MockBean
private SecurityContext securityContext;
#MockBean
private Authentication authentication;
#MockBean
private EmployeeRepository employeeRepository;
#BeforeClass
public static void setUp() {
}
#Before
#Override
public void resetMocks() {
reset(employeeRepository);
}
#Test
public void meGet() throws Exception {
when(securityContext.getAuthentication()).thenReturn(authentication);
securityContext.setAuthentication(authentication);
when(authentication.getPrincipal()).thenReturn(mockEmployee());
SecurityContextHolder.setContext(securityContext);
when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());
ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}
If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:
org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'
Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the #ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.
I could use PowerMock but I would like to avoid it if possible.
Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it.
You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.
AccessToken mockAccessToken = mock(AccessToken.class);
Authentication authentication = mock(Authentication.class);
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);
I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the #WithMockUser annotation was working properly.
What's key for this to work is using these snippets at class level:
#WebMvcTest(MeController.class)
#Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
Using #WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with #WithMockUser or mocking the service that handles such a call.
This sample code is working for me. This code is using JUnit 5.
#SpringBootTest(classes = Application.class)
#AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {
// mockMvc is not #Autowired because I am customizing it #BeforeEach
private MockMvc mockMvc;
#Autowired
private WebApplicationContext context;
#Mock
DefaultOidcUser principal;
#BeforeEach
public void beforeEach() {
Authentication authentication = mock(OAuth2AuthenticationToken.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = mock(SecurityContext.class);
when(securityContext.getAuthentication()).thenReturn(authentication);
when(authentication.getPrincipal()).thenReturn(principal);
SecurityContextHolder.setContext(securityContext);
// setting mockMvc with custom securityContext
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
#Test
public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {
final String loginName = "admin";
// given
// manipulate the principal as needed
when(principal.getAttribute("unique_name")).thenReturn(loginName);
// #formatter:off
// when
this.mockMvc.perform(get("/login"))
.andDo(print())
//then
.andExpect(status().isFound())
.andExpect(redirectedUrl("/logout"));
// #formatter:off
}
}

Resources