tutorial mvc mkyong form example java spring spring-mvc junit

java - mkyong - Cómo iniciar sesión un usuario con Spring 3.2 nuevas pruebas de mvc



spring mvc example (6)

Alguna solución con el director no funcionó para mí, por lo tanto, me gustaría mencionar otra salida:

mockMvc.perform(get("your/url/{id}", 5).with(user("anyUserName")))

Esto funciona bien hasta que tengo que probar un servicio que necesita un usuario registrado. ¿Cómo agrego usuario al contexto?

@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(); }


Con Spring 4, esta solución simula el inicio de sesión y el cierre de sesión utilizando sesiones y no cookies porque la prueba de seguridad Spring no devuelve cookies.

Como no es una práctica recomendada heredar pruebas, puede @Autowire este componente en sus pruebas y llamar a sus métodos.

Con esta solución, cada operación de ejecución en el mockMvc se llamará como autenticada si usted llamó a performLogin al final de la prueba a la que puede llamar a 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)); } }

Debido a que LogoutRequestBuilder predeterminado no admite la sesión, necesitamos crear otro generador de solicitudes de cierre de sesión.

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''s 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; } }

Después de llamar a la operación performLogin , toda su solicitud en la prueba se realizará automáticamente como usuario registrado.


Debería poder simplemente agregar el usuario al contexto de seguridad:

List<GrantedAuthority> list = new ArrayList<GrantedAuthority>(); list.add(new GrantedAuthorityImpl("ROLE_USER")); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, password,list); SecurityContextHolder.getContext().setAuthentication(auth);


Otra forma más ... uso las siguientes anotaciones:

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

(source)


Si desea usar MockMVC con el último paquete de prueba de seguridad Spring, pruebe este código:

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

Tenga en cuenta que debe utilizar la autenticación basada en Principal para que esto funcione.


Si la autenticación exitosa produce alguna cookie, entonces puede capturar eso (o solo todas las cookies) y pasarlo en las siguientes pruebas:

@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)); }

Aunque sé que no estoy haciendo ninguna solicitud de HTTP aquí, me gusta la separación más estricta de la prueba de integración anterior y mis controladores y la implementación de Spring Security.

Para hacer que el código sea un poco menos detallado, uso lo siguiente para combinar las cookies después de realizar cada solicitud, y luego las paso en cada solicitud posterior:

/** * 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()]); }

... y un pequeño ayudante como cookie(...) necesita al menos una 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") }; }

... para terminar con:

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);