How to mock spring security cookies session in spring boot unit Test? - spring-boot

I have added Http cookie Authentication using authentication manager to my Spring Boot REST API
I have a controller that exposes a rest service allowing authentication to /api/auth/signin resource via Spring security cookies session.
Here is the the Controller and the security configuration This exemple.
After running the application, I noticed that it is important to carry out the unit test part, so I wanted to create mocks for the authenticateUser method (resource: /signin), but unfortunately I encountered problems.
Voici la classe AuthControllerTest:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes=Application.class)
#WebMvcTest(AuthController.class)
public class AuthControllerTest {
#MockBean
UserRepository userRepository;
#MockBean
AuthenticationManager authenticationManager;
#MockBean
private UserDetailsServiceImpl userDetailsServiceImpl;
#Autowired
private MockMvc mockMvc;
private static UserDetailsImpl dummy;
#MockBean
private JwtUtils jwtUtil;
#Autowired
WebApplicationContext webApplicationContext ;
private ResponseCookie cookies;
#BeforeEach
public void setUp() {
dummy = new UserDetailsImpl(10L,"test1","test1#mail.com","123456",new ArrayList<>());
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();*/
cookies = jwtUtil.generateJwtCookie(dummy) ;
}
#Test
#DisplayName("POST /signin")
void authenticateUser() throws Exception
{
LoginRequest authenticationRequest = new LoginRequest("mod", "123456") ;
String jsonRequest = asJsonString(authenticationRequest);
RequestBuilder request = MockMvcRequestBuilders
.post("/api/auth/signin")
.content(jsonRequest)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON);
Authentication auth = Mockito.mock(Authentication.class);
Mockito.when(auth.getName()).thenReturn("authName");
auth.setAuthenticated(true);
Mockito.when(auth.isAuthenticated()).thenReturn(true);
Mockito.when(authenticationManager.authenticate(auth)).thenReturn(auth); // Failing here
Mockito.when(jwtUtil.generateJwtCookie(dummy)).thenReturn(cookies);
Mockito.when(userDetailsServiceImpl.loadUserByUsername("test1")).thenReturn(dummy);
MvcResult mvcResult = mockMvc.perform(request)
.andExpect(status().is2xxSuccessful())
.andReturn();
}
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Here is the encountered errors after running the class AuthControllerTest:
java.lang.AssertionError: Range for response status value 403
expected: but was:<CLIENT_ERROR> Expected :SUCCESSFUL
Actual :CLIENT_ERROR
at
org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at
org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at
org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$is2xxSuccessful$3(StatusResultMatchers.java:78)
at
org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212)
at AuthControllerTest.authenticateUser(AuthControllerTest.java:102)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498) at
org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at
org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at
org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at
org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at
org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at
org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at
org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at
org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at
org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

If you willing to change your code, then do this and hopefully everything will work fine:
A. Create a package in your test main package, it should include both words test and integration
package com.<yourApplication>.test.integration;
B.This is how your test class should be like:
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#Import({ ObjectMapper.class, <YourController>.class })
#TestMethodOrder(OrderAnnotation.class)
class YourTestClass {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
// user authentication
private static String jwt; // can use this for your next test request
#Test
#Order(1)
#DisplayName("User Authentication token")
void authenticationTest() throws JsonProcessingException, Exception {
final String link = "/api/auth/signin";
AuthenticationRequest defaultAuth = new AuthenticationRequest("admin", "admin");
System.out.println(objectMapper.writeValueAsString(defaultAuth));
// perform the request
MvcResult result = this.mockMvc
.perform(MockMvcRequestBuilders.post(link)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(defaultAuth)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn();
String response = result.getResponse().getContentAsString();
System.out.println("from response: " + response); //
JsonNode root = objectMapper.readTree(response);
JsonNode jwtvalue = root.get("jwt");
jwt = jwtvalue.textValue();
System.out.println("jwt deserlized: " + jwt);
}
}
C. If the request returned an error, then the problem is either in your controller or the way you setup the JWT authentication.

Related

How to inject a test UserDetailsManager into CustomProviderManager during SpringBootTest?

Given is a Spring Boot application with a custom ProviderManager:
#Component
public class CustomProviderManager extends ProviderManager {
public CustomProviderManager(
AuthenticationProvider internalAuthenticationProvider,
AuthenticationProvider devUserAuthenticationProvider) {
super(internalAuthenticationProvider, devUserAuthenticationProvider);
}
}
The SecurityFilterChain is setup with a custom UsernamePasswordAuthenticationFilter:
#Bean
public SecurityFilterChain mvcFilterChain(HttpSecurity http) throws Exception {
return http
//....
.addFilterAt(internalUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
//....
}
And here the custom UsernamePasswordAuthenticationFilter:
#Component
public class InternalUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final SecurityContextRepository securityContextRepository;
private final AuthenticationFailureHandler authenticationFailureHandler;
private final AuthenticationSuccessHandler authenticationSuccessHandler;
#PostConstruct
private void setup() {
super.setUsernameParameter("identifier");
super.setPasswordParameter("password");
super.setFilterProcessesUrl("/authenticate");
super.setSecurityContextRepository(securityContextRepository);
super.setAuthenticationFailureHandler(authenticationFailureHandler);
super.setAuthenticationSuccessHandler(authenticationSuccessHandler);
super.afterPropertiesSet();
}
public InternalUsernamePasswordAuthenticationFilter(
AuthenticationManager customProviderManager,
SecurityContextRepository delegatingSecurityContextRepository,
AuthenticationFailureHandler authenticationFailureHandler,
AuthenticationSuccessHandler authenticationSuccessHandler) {
this.securityContextRepository = delegatingSecurityContextRepository;
this.authenticationFailureHandler = authenticationFailureHandler;
this.authenticationSuccessHandler = authenticationSuccessHandler;
super.setAuthenticationManager(customProviderManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//....
}
}
What I want to do now is testing the authentication logic. But instead of using the authentication providers of the application, I want to use a special UserDetailsManager for testing only. The current TestConfiguration class containing a TestUserDetailsManager looks like that:
#TestConfiguration
public class TestUserDetailsManagerConfig {
#Bean
#Primary
public UserDetailsManager testUserDetailsManager() {
User.UserBuilder users = User.builder();
UserDetails testUser = users
.username("test-user#example.com")
.password("test-user")
.roles("USER")
.build();
UserDetails testAdmin = users
.username("test-admin#example.com")
.password("test-admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(testUser, testAdmin);
}
}
And finally, a test method that should authenticate against the TestUserDetailsManager:
#SpringBootTest
#Import(TestUserDetailsManagerConfig.class)
public class InternalAuthenticationTest {
#Autowired WebApplicationContext context;
MockMvc mvc;
#BeforeEach
void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
void form_login_redirects_role_admin_to_page_admin_after_authentication() throws Exception {
MvcResult result = mvc
.perform(SecurityMockMvcRequestBuilders
.formLogin()
.loginProcessingUrl("/authenticate")
.user("identifier", "test-admin#example.com")
.password("password", "test-admin"))
.andExpect(MockMvcResultMatchers.redirectedUrl(AUTH_LOGIN_SUCCESS_ADMIN_REDIRECT_URL))
.andExpect(SecurityMockMvcResultMatchers.authenticated()
.withUsername("test-admin#example.com").withRoles("ADMIN")
.withAuthentication(auth -> assertThat(auth).isInstanceOf(UsernamePasswordAuthenticationToken.class)))
.andReturn();
}
}
My naive approach unfortunately does not work, and as the log shows, the authentication checks are done against the application provider, but not against the TestUserDetailsManager:
Invoking InternalUsernamePasswordAuthenticationFilter (7/12)
Authenticating request with InternalAuthenticationProvider (1/2)
Failed to find user credential for email 'test-admin#example.com'
Authenticating request with $Proxy157 (2/2)
Failed to find user 'test-admin#example.com'
Failed to process authentication request
-> Bad credentials
My question now:
How can I inject the TestUserDetailsManager into the CustomProviderManager so that the authentication (not authorization) tests work with special test users?
edit:
The question somewhat more generally:
How can I test the authentication of a Spring Boot application using a special UserDetailsManager for test cases only?
Many thanks in advance

How to add authentication to controller tests Spring?

I added a basic auth to my Spring app in application.properties like this:
#Security
security:
user:
name: admin
password: admin
I also added spring-boot-starter-security to make it work. Now I'm curious how I need to refactor controller tests as after I implemented basic auth all of those tests returned 401 status. I am using junit5 and mockito. This is the ControllerTests class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = BooksApplication.class)
#AutoConfigureMockMvc
class BookControllerTests {
#Autowired
MockMvc mockMvc;
#Autowired
ObjectMapper mapper;
#MockBean
BookController bookController;
#Autowired
private BookConverter converter;
#Test
public void createBook_success() throws Exception {
Book book = Book.builder().name("Great book").author("Ivan")
.pagesNumber(340).publisher("New Publisher").build();
BookDto dto = converter.toDto(book);
given(bookController.createBook(dto)).willReturn(dto);
mockMvc.perform(post("/api/books/create")
.contentType(MediaType.APPLICATION_JSON).content(asJsonString(book)))
.andExpect(status().isCreated());
}
#Test
public void getAllBooks_success() throws Exception {
Book book1 = Book.builder().name("New book").author("Ivan").build();
Book book2 = Book.builder().name("This book").author("Whatever").build();
List<Book> bookList = new ArrayList<>(Arrays.asList(book1, book2));
Mockito.when(bookController.getAllBooks()).thenReturn(bookList);
mockMvc.perform(MockMvcRequestBuilders.get("/api/books/all")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].name", is("New book")));
}
#Test
public void findByTitle_success() throws Exception {
Book book = Book.builder().name("New book").author("Ivan")
.pagesNumber(340).publisher("New Publisher").build();
BookDto dto = converter.toDto(book);
Mockito.when(bookController.findByTitle(book.getName())).thenReturn(dto);
LinkedMultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("name", "New book");
mockMvc.perform(MockMvcRequestBuilders.get("/api/books/byTitle").params(requestParams)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
private String asJsonString(final Object obj){
try{
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e){
throw new RuntimeException(e);
}
}
}
P.S. Is there any article or documentation about implementing authentication in tests? Will be appreciated.
Check the Spring Security Documentation - https://docs.spring.io/spring-security/reference/5.6.5/servlet/test/method.html#test-method-withmockuser
You can use the #WithMockUser annotation, which provides default credentials with username - user and password - password, and with the role - ROLE_USER

Mockmvc returns empty response body even if response status is 200

I am trying to learn Junit and I ended up in a situation where my testcase returns 200 status code but returns null response Body. Its just a simple save operation using JPA repo and I have tried many online solutions but none worked for me.
Testclass :
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
class CarManufacturingSystemApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private GroupController groupController;
ObjectMapper om = new ObjectMapper();
#Test
public void createGroupTest() throws Exception {
GroupCreateRequest createRequest = new GroupCreateRequest();
createRequest.setActiveFlag(true);
createRequest.setGroupName("test");
createRequest.setPlantCode(1L);
String expectedjson = "{\r\n" + "\"message\": \"Group created successfully\"\r\n" + "}";
MvcResult result = mockMvc.perform(post("/group/createGroup")
.contentType(MediaType.APPLICATION_JSON_VALUE).accept(MediaType.APPLICATION_JSON_VALUE).content(new Gson().toJson(createRequest)))
.andExpect(status().isOk())
.andReturn();
String actualJson = result.getResponse().getContentAsString();
Assert.assertEquals(expectedjson, actualJson);
}
Controller:
#RestController
#RequestMapping(value = "/group")
public class GroupController {
#Autowired
private GroupService groupService;
#PostMapping("/createGroup")
public ResponseEntity<Response> createGroup(#RequestBody GroupCreateRequest groupCreateRequest) {
Response response = groupService.createGroup(groupCreateRequest);
return new ResponseEntity<> (response, HttpStatus.OK);
}
}
Error:
org.junit.ComparisonFailure: expected:<[{
"message": "Group created successfully"
}]> but was:<[]>
at org.junit.Assert.assertEquals(Assert.java:115)
at org.junit.Assert.assertEquals(Assert.java:144)
at com.nissan.car.manufacturing.system.CarManufacturingSystemApplicationTests.createGroupTest(CarManufacturingSystemApplicationTests.java:87)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
Service implementation
public Response createGroup(GroupCreateRequest groupCreateRequest) {
Group group = new Group();
Response response = new Response();
try {
addGroupDetails(groupCreateRequest, group);
groupRepository.save(group);
Note that your are testing GroupController, not GroupService, so you should mock the GroupService. Please replace
#MockBean
private GroupController groupController;
to
#MockBean
private GroupService groupService;
And then using simple stubbing directives when(something).thenReturn(somethingElse) to make your groupService return the response you specified.
#Test
public void createGroupTest() throws Exception {
// ...
Response response = new Response();
response.setMessage("Group created successfully");
when(groupService.createGroup(any())).thenReturn(response);
// ...
Assert.assertEquals(expectedjson, actualJson);
}

MockMvc does not return created resource in post response

Normally when you post to a spring data rest endpoint the response contains the location header with the url to the newly created resource and the json representation of the new resource in its body.
But when I post to MockMvc like following:
#RunWith(SpringRunner.class)
#SpringBootTest
public class OrderRestTest {
#Autowired
private ObjectMapper objectMapper;
#Autowired
private WebApplicationContext context;
#Autowired
private OAuthHelper oAuthHelper;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
public void testSuperuserCanCreateOrder() throws Exception {
RequestPostProcessor accessToken = oAuthHelper.addBearerToken("someSuperUser", "ROLE_SUPERUSER");
Order order = new Order();
order.salesMemo = "some sales memo";
String responseFromTestRestTemplate = objectMapper.writeValueAsString(order);
assertNotNull(responseFromTestRestTemplate);
mockMvc.perform(
post("/ErPApI/orders")
.with(accessToken)
.content(objectMapper.writeValueAsString(order))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().is2xxSuccessful());
mockMvc.perform(
get("/ErPApI/orders")
.with(accessToken))
.andExpect(jsonPath("_embedded.orders", hasSize(1)))
.andExpect(jsonPath("_embedded.orders[0].salesMemo", is("some sales memo")))
.andReturn();
}
}
the post is successful but the response body is blank. Is there a way to simulate the real response with MockMvc? Is my setup wrong?
set the Accept header to application/json too in the request.

How to test a resource with OAuth2 and Mock

I am using Jhipster with Oauth2 implementation and mongodb as a database.
I am trying to test a resource with OAuth2. But I got always an error message "Access Denied" and status code 401. I am looking for an JUnit example with OAuth2. Thank you!
Manuel
/**
* Test class for the InvoiceResource REST controller.
*
* #see InvoiceResource
*/
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest
public class InvoiceResourceIntTest {
...
private MockMvc restInvoiceMockMvcWebApp;
#PostConstruct
public void setup() {
MockitoAnnotations.initMocks(this);
this.restInvoiceMockMvcWebApp = MockMvcBuilders.webAppContextSetup(context).alwaysDo(MockMvcResultHandlers.print())
.apply(SecurityMockMvcConfigurers.springSecurity()).build();
}
#Before
public void initTest() {
// Create currentuser
currentUser = new User();
currentUser.setActivated(CURRENTUSER_ACTIVATED);
currentUser.setFirstName(CURRENTUSER_FIRSTNAME);
currentUser.setLastName(CURRENTUSER_LASTNAME);
currentUser.setEmail(CURRENTUSER_EMAIL);
Set<Authority> authorities = new HashSet<>();
Authority authority = new Authority();
authority.setName(AuthoritiesConstants.ADMIN);
currentUser.setAuthorities(authorities);
currentUser.setPassword(passwordEncoder.encode(CURRENTUSER_PASSWORD));
userRepository.save(currentUser);
}
#Test
// #WithMockUser(username = CURRENTUSER_EMAIL, password = CURRENTUSER_PASSWORD, roles = { "ADMIN" })
public void getAllInvoices() throws Exception {
// Initialize the database
invoice.setDeletedAt(LocalDate.now());
invoiceRepository.save(invoice);
invoice.setId(null);
invoice.setDeletedAt(null);
invoiceRepository.save(invoice);
// Get all the invoices
restInvoiceMockMvcWebApp.perform(get("/api/invoicessort=id,desc")
.with(user(CURRENTUSER_EMAIL).password(CURRENTUSER_PASSWORD.roles("ADMIN")))
.andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$", hasSize(1)))
}
You can get a token from the token resource, and use that token in your tests, here a complete example.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
public class PermissionTest {
#Autowired
WebApplicationContext context;
#Autowired
FilterChainProxy springSecurityFilterChain;
MockMvc mvc;
#Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void shouldHavePermission() throws Exception {
mvc.perform(get("/api/resource")
.header("Authorization", "Bearer " + getAccessToken("user", "123"))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
private String getAccessToken(String username, String password) {
MockHttpServletResponse response = mvc
.perform(post("/oauth/token")
.header("Authorization", "Basic "
+ new String(Base64Utils.encode(("appclient:password")
.getBytes())))
.param("username", username)
.param("password", password)
.param("grant_type", "password"))
.andReturn().getResponse();
return new ObjectMapper()
.readValue(response.getContentAsByteArray(), OAuthToken.class)
.accessToken;
}
#JsonIgnoreProperties(ignoreUnknown = true)
private static class OAuthToken {
#JsonProperty("access_token")
public String accessToken;
}
}

Resources