如何使用 spring 3.2 新 mvc 测试登录用户
Posted
技术标签:
【中文标题】如何使用 spring 3.2 新 mvc 测试登录用户【英文标题】:How to login a user with spring 3.2 new mvc testing 【发布时间】:2012-12-27 19:35:47 【问题描述】:这工作正常,直到我必须测试需要登录用户的服务,我如何将用户添加到上下文:
@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();
【问题讨论】:
另见Spring mvc 3.1 integration tests with session support。 【参考方案1】:您应该能够将用户添加到安全上下文中:
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
list.add(new GrantedAuthorityImpl("ROLE_USER"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, password,list);
SecurityContextHolder.getContext().setAuthentication(auth);
【讨论】:
是的,谢谢这修复了它,我想知道 spring 3.2 和 mockMvc 是否有新方法【参考方案2】:如果你想使用 MockMVC 和最新的 spring 安全测试包,试试这个代码:
Principal principal = new Principal()
@Override
public String getName()
return "TEST_PRINCIPAL";
;
getMockMvc().perform(get("http://your-url.com").principal(principal))
.andExpect(status().isOk()));
请记住,您必须使用基于主体的身份验证才能使其正常工作。
【讨论】:
另一种可能是使用 com.sun.security.auth.UserPrincipal:getMockMvc().perform(get("http://your-url.com").principal(new UserPrincipal("TEST_USER_ID"))).andExpect(status().isOk()));
【参考方案3】:
如果成功的身份验证产生了一些 cookie,那么您可以捕获它(或仅捕获所有 cookie),并在接下来的测试中传递它:
@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));
虽然我知道我没有在这里发出任何 HTTP 请求,但我有点喜欢上述集成测试与我的控制器和 Spring Security 实现更严格的分离。
为了使代码不那么冗长,我在发出每个请求后使用以下内容合并 cookie,然后在每个后续请求中传递这些 cookie:
/**
* 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()]);
...像cookie(...)
这样的小助手至少需要一个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") ;
...结束:
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);
【讨论】:
我知道这是一个旧答案,但无论如何我都会尝试:我想以完全相同的方式获取 cookie,但不知何故,我的 response().getCookies() 返回一个空数组。但我有百分之一的把握,该请求返回一个 cookie。 你在使用andReturn()
吗?那我就不知道了,抱歉。
我也有同样的问题【参考方案4】:
为什么校长的解决方案对我不起作用,因此,我想提另一个出路:
mockMvc.perform(get("your/url/id", 5).with(user("anyUserName")))
【讨论】:
导入静态 org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;【参考方案5】:在 spring 4 中,此解决方案使用会话而不是 cookie 模拟 formLogin 和注销,因为 spring 安全测试不返回 cookie。
因为继承测试不是最佳实践,您可以在测试中@Autowire 这个组件并调用它的方法。
使用此解决方案,如果您在测试结束时调用performLogin
,则在mockMvc
上执行的每个操作都将被称为已验证,您可以调用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));
因为默认的LogoutRequestBuilder
不支持会话,我们需要创建另一个注销请求生成器。
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;
调用performLogin
操作后,您在测试中的所有请求都将自动以登录用户身份执行。
【讨论】:
【参考方案6】:另一种方式...我使用以下注释:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners(listeners=ServletTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
WithSecurityContextTestExcecutionListener.class)
@WithMockUser
public class WithMockUserTests
...
(source)
【讨论】:
以上是关于如何使用 spring 3.2 新 mvc 测试登录用户的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC 3.2 Thymeleaf Ajax 片段
Spring MVC 3.2 和 HTML5 中的 Web 套接字
如何使用 Spring MVC 测试避免“圆形视图路径”异常
在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc