Spring boot integration test Authentication - spring-boot

Hello everyone in my application im using oauth2.0 for authentication in my controller methods im always checking if Authentication argument is always set.
public ResponseEntity<?> getFarm(Authentication authentication) {
if (authentication == null) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
everything works fine but im trying to write integration test for this and I have problem with mocking the authentication i was trying many solutions there like : Spring Test & Security: How to mock authentication? but still im getting null.
this is my integration test
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestRest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void findAllCustomers() {
var farmInfoDto = new FarmInfoDto("big farm", "warsaw", "strett", "123", "15a");
var test = restTemplate.withBasicAuth("test","test").postForEntity("/farm", farmInfoDto, Farm.class);
var farm = test.getBody();
assert farm.getFarmName().equals(farmInfoDto.farmName());
}
}

Related

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.

Spring Data Rest cannot do Integration test?

I have tried to use both MockMVC and TestRestTemplate. In both cases, the response back is 404 but the API endpoints work outside of integration test (when I run the spring app on its own).
Does anyone have a working sample app that has a working integration test for a generated controller using Spring Data Rest?
I was also able to write regular integration tests against my own controllers (Non SDR types)
Test code:
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testApi() {
String settings = testRestTemplate
.getForObject("/api/v1/orders", String.class);
System.out.println(settings);
}
}
Repo:
#RepositoryRestResource(excerptProjection = OrderSummaryProjection.class)
public interface OrderRepository extends JpaRepository<Order, Long> {}
Ok I found out the issue but I dont know what the answer should be:
I set spring.data.rest.basePath in application.properties.
But I don't think that file is read when you run the integration tests. How do I fix that?
I currently don't test Spring Data Rest endpoints, but if I were to do it, I would test interfaces using classical Integration test approach:
#RunWith(SpringRunner.class)
#SpringBootTest
public class DummyIT {
#Autowired
private SettingsRepository settingsRepository;
#Test
public void testApi() {
List<Settings> settings = settingsRepository.findAll();
assertNotNull(settings);
}
}
I also tested end-to-end test and it also works, it just returns ugly {"_embedded" : {"settings" : [ { ... } ] }, ... } so it's doable, but it's not pretty:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DummyTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void testApi() {
String settings = testRestTemplate
.getForObject("/api/settings", String.class);
System.out.println(settings);
}
}

Spring Security and MockMvc - Need to mock authentication or principal

I'm using Spring Security, and facing issue writing unit test case (using MockMvc) for a controller.
I have a method in my controller that goes something like this:
#GetMapping
public ResponseEntity<User> getUser(#AuthenticationPrincipal User activeUser){
String userEmail = activeUser.getEmail();
return userService.getUser(userEmail);
}
I get a 500 error with this.
Another variation for the controller I've tried is, and this is working on Postman/Curl :
#GetMapping
public ResponseEntity<User> getUser(OAuth2Authentication authentication){
String userEmail = (String) authentication.getUserAuthentication().getPrincipal();
return userService.getUser(userEmail);
}
My service looks like :
public ResponseEntity<User> getUser(String email) {
return userRepository.findByEmail(email)
.map(record -> ResponseEntity.ok().body(record))
.orElse(ResponseEntity.notFound().build());
}
In my unit test case for this controller method, I have:
#Test
#WithMockUser(username = "1", password = "pwd", roles = "USER")
public void controller_should_get_user() throws Exception {
when(userService.getUser("1")).thenReturn(new ResponseEntity(userMock, HttpStatus.OK));
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/user/")).andExpect(status().isOk());
}
I am getting the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
at com.timecloud.user.controller.UserControllerTest.controller_should_get_user(UserControllerTest.java:60)
How should I go about passing or mocking a user with the current authentication? Thanks.
#WithMockUser creates a UsernameAuthenticationToken, not an OAuth2Authentication.
At least three solutions here:
Inject an OAuth2Authentication mock or instance in the security context
change your method to public ResponseEntity<User> getUser(Authentication authentication), then using authentication.getName() inside
use some existing tooling to apply solution 1. for you, like in this libs I wrote
Sample usage with solution 1
#Test
public void test() throws Exception {
final var storedRequest = mock(OAuth2Request);
final var principal = mock(Principal.class);
when(principal.getName()).thenReturn("user");
final var userAuthentication = mock(Authentication.class);
when(userAuthentication.getAuthorities()).thenReturn(Set.of(new SimpleGrantedAuthority("ROLE_USER"));
when(userAuthentication.getPrincipal()).thenReturn(principal);
final var oauth2Authentication = new OAuth2Authentication(storedRequest, authentication);
SecurityContextHolder.getContext().setAuthentication(oauth2Authentication);
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
Sample usage with solution 3
#Test
#WithMockAuthentication(authType = OAuth2Authentication.class, name = "user", authorities = "ROLE_USER")
public void test() throws Exception {
// use MockMvc to test a #Controller or unit-test any other secured #Component as usual
}
NullPointerException is coming because your test is unable to find anything for OAuth2Authentication Object. There are two things you can do your test case:
Try Mocking OAuth2Authentication in some setUp method.
OR
If you are using Spring 4.0+, the best solution is to annotate the test method with #WithMockUser
#Test
#WithMockUser(username = "user1", password = "pwd", roles = "USER")
public void mytest1() throws Exception {
//Your test scenario
}

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
}
}

Testing a Spring Boot application with custom ErrorAttributes?

I am trying to test a Spring Boot RestController that should use custom error attributes.
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(
RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Throwable error = getError(requestAttributes);
return errorAttributes;
}
};
}
But when i try to test the custom error attributes using a simple test these properties are not taken into account. The test below actually fires a request and i except that the custom attributes are used. But whatever i seem to do the code seems to be not taken into account.
class TestSpec extends Specification {
MockMvc mockMvc
def setup() {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build()
}
def "Test simple action"() {
when:
def response = mockMvc.perform(post("/hello")
.contentType(MediaType.APPLICATION_JSON)
.content('{"sayHelloTo": ""}')
)
then:
response.andExpect(status().isOk())
}
}
Any clue on how i could test if the custom attributes?
Spring Boot's error infrastructure works by forwarding requests to an error controller. It's this error controller that uses an ErrorAttributes instance. MockMvc only had fairly basic support for testing the forwarding of requests (you can check that the request would be forwarded, but not the actual outcome of that forward). This means that a MockMvc test that calls your HellowWorldController, either using standalone setup or a web application context-based setup, isn't going to drive the right code path.
A few options:
Unit test your custom ErrorAttributes class directly
Write a MockMvc-based test that calls Spring Boot's BasicErrorController configured with your custom ErrorAttributes instance
Write an integration test that uses RestTemplate to make an actual HTTP call into your service
The test class from Spring gives you a good place to start your own tests!
Create an instance of your custom error attributes class and use MockHttpServletRequest and WebRequest:
private final DefaultErrorAttributes errorAttributes = new YourCustomErrorAttributes();
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final WebRequest webRequest = new ServletWebRequest(this.request);
For your test method:
//Set the appropriate error state in your mocked request object:
RuntimeException ex = new RuntimeException("Test");
this.request.setAttribute("javax.servlet.error.exception", ex);
//Pass the mocked request into the the methods that are normally called by the framework
Map<String, Object> attributes = this.errorAttributes.getErrorAttributes(this.webRequest, ErrorAttributeOptions.of(Include.STACK_TRACE));
// add your own asserts
assertThat(attributes.get("trace").toString()).startsWith("java.lang");

Resources