11

I have a spring boot application which requires login for some actions. I am trying to test them using MockMvc, but it doesn't seem to work. I keep getting a HTTP response with status 403 (forbidden). Probably there is something wrong with the authentication part.

I have tried following the documentation, but I wasn't able to get it working.

This is my current testing code:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebIntegrationTest("server.port = 8093")
public class PasswordChangeTests {
    @Autowired
    private EmbeddedWebApplicationContext webApplicationContext;

    @Autowired
    private UserRepository userRepository;

    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(webApplicationContext)
                .apply(springSecurity())
                .build();
    }

     @Test
     public void changePasswordWorks() throws Exception {
         // Send password change request
         PasswordChangeRepresentation passwordChange = new PasswordChangeRepresentation(DefaultUsers.Admin.getPassword(), "12345678");
         mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST, "/password/change")
                 .content(new ObjectMapper().writeValueAsString(passwordChange))
                 .contentType(MediaType.APPLICATION_JSON)
                 .accept(MediaType.APPLICATION_JSON))
                 .andExpect(status().isOk());

         // Check that the password has been changed
         User user = this.userRepository.findByUsername(DefaultUsers.Admin.getEmail());
         Assert.assertEquals(user.getPassword(), "12345678");
    }
}

Sorry if I am missing something obvious. This is my first experience with spring boot.

aochagavia
  • 5,887
  • 5
  • 34
  • 53
  • 1
    I'm curious about your `springSecurity()` method. Could you please show the method body of that method? – ksokol Apr 17 '15 at 16:25
  • 1
    The springSecurity() method is provided by Spring Security. It adds the springSecurityFilterChain filter and ensures that the testing support is integrated into the Filter. – Rob Winch Apr 17 '15 at 16:53

1 Answers1

27

You need to specify which user you want to run the test as. You have a few options (each option is a link to the detailed documentation):

@WithMockUser

This option will create a fake user (i.e. the user does not need to exist in a data store). The problem with this approach is if your application relies on a custom User implementation you may get class cast Exceptions. If you do not return a custom type from a custom UserDetailsService, then this solution should work fine.

 @Test
 @WithMockUser(username="admin",roles={"USER","ADMIN"})
 public void changePasswordWorks() throws Exception {

@WithUserDetails

If you implemented a custom UserDetailsService that returns a custom implementation of UserDetails, this solution may work for you.

For it to work you need to expose a UserDetailsService as a Bean and the user must exist. For example:

 @Test
 @WithUserDetails("admin")
 public void changePasswordWorks() throws Exception {

@WithSecurityContext

This is the best of both worlds, but requires a little additional setup. If you have a custom UserDetailsService returning a custom implementation of UserDetails and do NOT want the user to necessarily have to exist you can use this method. I'll let you read the documentation on this setup as it is a bit more lengthy and well documented.

Using a RequestPostProcessor

If annotations aren't your thing you can use a RequestPostProcessor. For example:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

 ...

 @Test
 public void changePasswordWorks() throws Exception {
     // Send password change request
     PasswordChangeRepresentation passwordChange = new PasswordChangeRepresentation(DefaultUsers.Admin.getPassword(), "12345678");
     mockMvc.perform(MockMvcRequestBuilders.request(HttpMethod.POST, "/password/change")

             // ADD this line
             .with(user("admin").roles("USER","ADMIN"))

             .content(new ObjectMapper().writeValueAsString(passwordChange))
             .contentType(MediaType.APPLICATION_JSON)
             .accept(MediaType.APPLICATION_JSON))
             .andExpect(status().isOk());

     // Check that the password has been changed
     User user = this.userRepository.findByUsername(DefaultUsers.Admin.getEmail());
     Assert.assertEquals(user.getPassword(), "12345678");
}
Rob Winch
  • 21,440
  • 2
  • 59
  • 76
  • http://stackoverflow.com/questions/43073515/test-spring-mvc-controller-with-preauthorize-giving-403-access-denied any idea??? – Gurkha Mar 28 '17 at 15:48
  • I've tried all these but it doesn't work. I've a @Secured("ROLE_USER") annotation in the controller, and I'm not able to write a test passing a different role to have a failure. Any suggestion? – SG87 Jun 01 '18 at 07:52
  • Since Spring-Security ~5.4.x version you can force `@WithUserDetails` to launch after `@BeforeEach` Junit5 annotiation by setting up `setupBefore = TestExecutionEvent.TEST_EXECUTION`. Full example: `@WithUserDetails(value ="LOGIN", setupBefore = TestExecutionEvent.TEST_EXECUTION)`. You can also create method annotated with `@PostConstruct` which will save your user. In these cases you can provide user directly in test class. [link](https://docs.spring.io/spring-security/site/docs/current/reference/html5/#test-method-withuserdetails) _Spring-Security-Documentation_ – Falcon Feb 11 '21 at 12:59