Add user role to request in Spring MVC Test Framework

11.7k views Asked by At

Started studying Spring Test MVC Framework at the office today, it looks handy, but facing some serious trouble right off the bat. Spent a few hours googling, but couldn't find anything related to my issue.

Here's my very simple test class:

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = WebAppContext.class)
public class ControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = webAppContextSetup(wac).build();
    }

    @Test
    public void processFetchErrands() throws Exception {
        mockMvc.perform(post("/errands.do?fetchErrands=true"))
               .andExpect(status().isOk())
               .andExpect(model().attribute("errandsModel", allOf(
                   hasProperty("errandsFetched", is(true)),
                   hasProperty("showReminder", is(false)))));
    }
}

The test reaches the following controller, but fails on first if clause thanks to not being authorized properly.

@RequestMapping(method = RequestMethod.POST, params="fetchErrands")
public String processHaeAsioinnit(HttpSession session, HttpServletRequest request, ModelMap modelMap,
                                  @ModelAttribute(ATTR_NAME_MODEL) @Valid ErrandsModel model,
                                  BindingResult result, JopoContext ctx) {
  if (request.isUserInRole(Authority.ERRANDS.getCode())) {
    return Page.NO_AUTHORITY.getCode();
  }

  [...]
}

How do I add a user role for the MockHttpServletRequest that gets created by MockMvcRequestBuilders.post(), so that I can get past the authority check on my controller?

I know MockHttpServletRequest has a method addUserRole(String role), but since MockMvcRequestBuilders.post() returns a MockHttpServletRequestBuilder, I never get my hands on the MockHttpServletRequest and thus cannot call that method.

Checking the Spring source, MockHttpServletRequestBuilder has no methods related to user roles, nor is the MockHttpServletRequest.addUserRole(String role) ever called in that class, so I have no idea how to tell it to add a user role into the request.

All I can think of is adding a custom filter to filter chain and calling a custom HttpServletRequestWrapper from there that provides an implementation of isUserInRole(), but that seems a little extreme for such a case. Surely the framework should offer something more practical?

5

There are 5 answers

1
Tk421 On BEST ANSWER

I think I found an easier way

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

    mockMvc.perform(get("/myurl"))
            .andExpect([...]);
}

You might need the following maven entry

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>4.0.4.RELEASE</version>
        <scope>test</scope>
    </dependency>
7
Angular University On

Spring MVC Test has the principal() method that allows to mock the request credentials for cases like this. This is an example of a test where some mock credentials are set:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:spring/mvc-dispatcher-servlet.xml")
public class MobileGatewayControllerTest {

private MockMvc mockMvc;

@Autowired
private WebApplicationContext wac;  

@Autowired
private Principal principal;

@Autowired
private MockServletContext servletContext;

@Before 
public void init()  {
    mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}


@Test
public void testExampleRequest() throws Exception {

    servletContext.declareRoles("ROLE_1");

    mockMvc.perform(get("/testjunit")
    .accept(MediaType.APPLICATION_JSON)
    .principal(principal))
    .andDo(print())
    .andExpect(status().isOk())
    .andExpect(content().contentType("application/json"))
    .andExpect(jsonPath("$.[1]").value("test"));
}

}

and this is an example of how to create a mock principal:

@Configuration
public class SetupTestConfig {

@Bean
public Principal createMockPrincipal()  {
    Principal principal = Mockito.mock(Principal.class);
    Mockito.when(principal.getName()).thenReturn("admin");  
    return principal;
}

}

0
Shehan Simen On

IF you are using Authority instead of Roles, then grant the authority in the request like below.

mockMvc.perform(
            put(REQUEST_URL).param(PARM, VALUE)
                    .with(SecurityMockMvcRequestPostProcessors.user(USERNAME).authorities(new SimpleGrantedAuthority("ADMIN")))                        
                    .contentType(APPLICATION_FORM_URLENCODED)
    )
0
qiubix On

There is also an easy, alternative way of setting up specific user role for a single request. It might be handy if you want to perform only a single operation as an authorized user (like set up the test fixture), and then check if user with different role can perform certain operation:

ResultActions registerNewUserAsAdmin(String username, String password) throws Exception {
    final SignUpRequest signUpPayload = new SignUpRequest(username, password);

    final MockHttpServletRequestBuilder registerUserRequest = post(SIGN_UP_URL)
        .with(user("admin").roles("ADMIN"))
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .content(jsonMapper.writeValueAsString(signUpPayload));

    return mockMvc.perform(registerUserRequest);
}

See SecurityMockMvcRequestPostProcessors for more details.

0
Shaun Thompson On

You can inject a RequestPostProcessor to configure the MockHttpServletRequest before it's used in the request.

MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/")
                    .with(new RoleRequestPostProcessor("some role"));

class RoleRequestPostProcessor implements RequestPostProcessor {
    private final String role;

    public RoleRequestPostProcessor(final String role) {
        this.role = role;
    }

    @Override
    public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
        request.addUserRole(role);
        return request;
    }
}