How to login using spring 3.2 new mvc test

This works fine until I check the service for which a registered user is needed, how to add a user to the context:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-test.xml") @WebAppConfiguration public class FooTest { @Autowired private WebApplicationContext webApplicationContext; private MockMvc mockMvc; @Resource(name = "aService") private AService aService; //uses logged in user @Before public void setup() { this.mockMvc = webAppContextSetup(this.webApplicationContext).build(); } 
+14
java spring spring-mvc junit
Jan 13 '13 at 21:26
source share
6 answers

If you want to use MockMVC with the latest spring security check package try this code:

 Principal principal = new Principal() { @Override public String getName() { return "TEST_PRINCIPAL"; } }; getMockMvc().perform(get("http://your-url.com").principal(principal)) .andExpect(status().isOk())); 

Keep in mind that for this you need to use principal-based authentication.

+15
Jan 14 '13 at 19:34
source share

If successful authentication gives some cookie, you can write this (or only all cookies) and pass it in the following tests:

 @Autowired private WebApplicationContext wac; @Autowired private FilterChainProxy filterChain; private MockMvc mockMvc; @Before public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac) .addFilter(filterChain).build(); } @Test public void testSession() throws Exception { // Login and save the cookie MvcResult result = mockMvc.perform(post("/session") .param("username", "john").param("password", "s3cr3t")).andReturn(); Cookie c = result.getResponse().getCookie("my-cookie"); assertThat(c.getValue().length(), greaterThan(10)); // No cookie; 401 Unauthorized mockMvc.perform(get("/personal").andExpect(status().isUnauthorized()); // With cookie; 200 OK mockMvc.perform(get("/personal").cookie(c)).andExpect(status().isOk()); // Logout, and ensure we're told to wipe the cookie result = mockMvc.perform(delete("/session").andReturn(); c = result.getResponse().getCookie("my-cookie"); assertThat(c.getValue().length(), is(0)); } 

Although I know that I am not making any HTTP requests here, I'm kind of like a more strict separation of the above integration test from my controllers and Spring's security implementation.

To make the code a little less verbose, I use the following to combine cookies after each request and then pass these cookies in each subsequent request:

 /** * Merges the (optional) existing array of Cookies with the response in the * given MockMvc ResultActions. * <p> * This only adds or deletes cookies. Officially, we should expire old * cookies. But we don't keep track of when they were created, and this is * not currently required in our tests. */ protected static Cookie[] updateCookies(final Cookie[] current, final ResultActions result) { final Map<String, Cookie> currentCookies = new HashMap<>(); if (current != null) { for (Cookie c : current) { currentCookies.put(c.getName(), c); } } final Cookie[] newCookies = result.andReturn().getResponse().getCookies(); for (Cookie newCookie : newCookies) { if (StringUtils.isBlank(newCookie.getValue())) { // An empty value implies we're told to delete the cookie currentCookies.remove(newCookie.getName()); } else { // Add, or replace: currentCookies.put(newCookie.getName(), newCookie); } } return currentCookies.values().toArray(new Cookie[currentCookies.size()]); } 

... and the little helper as a cookie(...) needs at least one cookie:

 /** * Creates an array with a dummy cookie, useful as Spring MockMvc * {@code cookie(...)} does not like {@code null} values or empty arrays. */ protected static Cookie[] initCookies() { return new Cookie[] { new Cookie("unittest-dummy", "dummy") }; } 

... eventually:

 Cookie[] cookies = initCookies(); ResultActions actions = mockMvc.perform(get("/personal").cookie(cookies) .andExpect(status().isUnauthorized()); cookies = updateCookies(cookies, actions); actions = mockMvc.perform(post("/session").cookie(cookies) .param("username", "john").param("password", "s3cr3t")); cookies = updateCookies(cookies, actions); actions = mockMvc.perform(get("/personal").cookie(cookies)) .andExpect(status().isOk()); cookies = updateCookies(cookies, actions); 
+10
Jan 24 '13 at 21:55
source share

You should simply add the user to the security context:

 List<GrantedAuthority> list = new ArrayList<GrantedAuthority>(); list.add(new GrantedAuthorityImpl("ROLE_USER")); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, password,list); SecurityContextHolder.getContext().setAuthentication(auth); 
+3
Jan 13 '13 at 21:28
source share

Since spring 4, this solution has a formLogin layout and logout using sessions, not cookies, because the spring security test does not return cookies.

Since this is not the best practice for test inheritance, you can @Autowire this component in your tests and call it methods.

With this solution, each operation on mockMvc will be called as authenticated if you called performLogin at the end of the test, which you can call performLogout .

 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import javax.servlet.Filter; import static com.condix.SessionLogoutRequestBuilder.sessionLogout; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Component public class SessionBasedMockMvc { private static final String HOME_PATH = "/"; private static final String LOGOUT_PATH = "/login?logout"; @Autowired private WebApplicationContext webApplicationContext; @Autowired private Filter springSecurityFilterChain; private MockMvc mockMvc; public MockMvc createSessionBasedMockMvc() { final MockHttpServletRequestBuilder defaultRequestBuilder = get("/dummy-path"); this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext) .defaultRequest(defaultRequestBuilder) .alwaysDo(result -> setSessionBackOnRequestBuilder(defaultRequestBuilder, result.getRequest())) .apply(springSecurity(springSecurityFilterChain)) .build(); return this.mockMvc; } public void performLogin(final String username, final String password) throws Exception { final ResultActions resultActions = this.mockMvc.perform(formLogin().user(username).password(password)); this.assertSuccessLogin(resultActions); } public void performLogout() throws Exception { final ResultActions resultActions = this.mockMvc.perform(sessionLogout()); this.assertSuccessLogout(resultActions); } private MockHttpServletRequest setSessionBackOnRequestBuilder(final MockHttpServletRequestBuilder requestBuilder, final MockHttpServletRequest request) { requestBuilder.session((MockHttpSession) request.getSession()); return request; } private void assertSuccessLogin(final ResultActions resultActions) throws Exception { resultActions.andExpect(status().isFound()) .andExpect(authenticated()) .andExpect(redirectedUrl(HOME_PATH)); } private void assertSuccessLogout(final ResultActions resultActions) throws Exception { resultActions.andExpect(status().isFound()) .andExpect(unauthenticated()) .andExpect(redirectedUrl(LOGOUT_PATH)); } } 

Since LogoutRequestBuilder does not support a session by default, we need to create another exit request constructor.

 import org.springframework.beans.Mergeable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.request.ConfigurableSmartRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import javax.servlet.ServletContext; import java.util.ArrayList; import java.util.List; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; /** * This is a logout request builder which allows to send the session on the request.<br/> * It also has more than one post processors.<br/> * <br/> * Unfortunately it won't trigger {@link org.springframework.security.core.session.SessionDestroyedEvent} because * that is triggered by {@link org.apache.catalina.session.StandardSessionFacade#invalidate()} in Tomcat and * for mocks it handled by @{{@link MockHttpSession#invalidate()}} so the log out message won't be visible for tests. */ public final class SessionLogoutRequestBuilder implements ConfigurableSmartRequestBuilder<SessionLogoutRequestBuilder>, Mergeable { private final List<RequestPostProcessor> postProcessors = new ArrayList<>(); private String logoutUrl = "/logout"; private MockHttpSession session; private SessionLogoutRequestBuilder() { this.postProcessors.add(csrf()); } static SessionLogoutRequestBuilder sessionLogout() { return new SessionLogoutRequestBuilder(); } @Override public MockHttpServletRequest buildRequest(final ServletContext servletContext) { return post(this.logoutUrl).session(session).buildRequest(servletContext); } public SessionLogoutRequestBuilder logoutUrl(final String logoutUrl) { this.logoutUrl = logoutUrl; return this; } public SessionLogoutRequestBuilder session(final MockHttpSession session) { Assert.notNull(session, "'session' must not be null"); this.session = session; return this; } @Override public boolean isMergeEnabled() { return true; } @SuppressWarnings("unchecked") @Override public Object merge(final Object parent) { if (parent == null) { return this; } if (parent instanceof MockHttpServletRequestBuilder) { final MockHttpServletRequestBuilder parentBuilder = (MockHttpServletRequestBuilder) parent; if (this.session == null) { this.session = (MockHttpSession) ReflectionTestUtils.getField(parentBuilder, "session"); } final List postProcessors = (List) ReflectionTestUtils.getField(parentBuilder, "postProcessors"); this.postProcessors.addAll(0, (List<RequestPostProcessor>) postProcessors); } else if (parent instanceof SessionLogoutRequestBuilder) { final SessionLogoutRequestBuilder parentBuilder = (SessionLogoutRequestBuilder) parent; if (!StringUtils.hasText(this.logoutUrl)) { this.logoutUrl = parentBuilder.logoutUrl; } if (this.session == null) { this.session = parentBuilder.session; } this.postProcessors.addAll(0, parentBuilder.postProcessors); } else { throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); } return this; } @Override public SessionLogoutRequestBuilder with(final RequestPostProcessor postProcessor) { Assert.notNull(postProcessor, "postProcessor is required"); this.postProcessors.add(postProcessor); return this; } @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { for (final RequestPostProcessor postProcessor : this.postProcessors) { request = postProcessor.postProcessRequest(request); if (request == null) { throw new IllegalStateException( "Post-processor [" + postProcessor.getClass().getName() + "] returned null"); } } return request; } } 

After the performLogin operation is performLogin all your request in the test will be automatically executed as a registered user.

+1
Nov 02 '17 at 7:15
source share

Some solution with the director did not work for me, so I would like to mention another output:

 mockMvc.perform(get("your/url/{id}", 5).with(user("anyUserName"))) 
0
Feb 03 '17 at 10:17
source share

Another way ... I use the following annotations:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @TestExecutionListeners(listeners={ServletTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, WithSecurityContextTestExcecutionListener.class}) @WithMockUser public class WithMockUserTests { ... } 

(source)

0
Dec 05 '17 at 19:34 on
source share



All Articles