java 如何使用 spring 3.2 新 mvc 测试登录用户
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14308341/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to login a user with spring 3.2 new mvc testing
提问by NimChimpsky
This works fine until I have to test a service that needs a logged in user, how do I add user to 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();
}
回答by seanbdoherty
If you want to use MockMVC with the latest spring security test package, try this code:
如果你想使用 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()));
Keep in mind that you have to be using Principal based authentication for this to work.
请记住,您必须使用基于主体的身份验证才能使其工作。
回答by Arjan
If successful authentication yields some cookie, then you can capture that (or just all cookies), and pass it along in the next tests:
如果成功的身份验证产生了一些 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));
}
Though I know I'm not making any HTTP requests here, I kind of like the stricter separation of the above integration test and my controllers and Spring Security implementation.
虽然我知道我不会在这里发出任何 HTTP 请求,但我有点喜欢上述集成测试与我的控制器和 Spring Security 实现的更严格的分离。
To make the code a bit less verbose, I use the following to merge the cookies after making each request, and then pass those cookies along in each subsequent request:
为了使代码不那么冗长,我使用以下内容在发出每个请求后合并 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()]);
}
...and a small helper as cookie(...)
needs at least one cookie:
...还有一个小帮手,因为它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") };
}
...to end up with:
...最终:
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);
回答by Andres Olarte
You should be able to just 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);
回答by ryzhman
Somewhy solution with principal didn't worked for me, thus, I'd like to mention another way out:
为什么校长的解决方案对我不起作用,因此,我想提到另一种出路:
mockMvc.perform(get("your/url/{id}", 5).with(user("anyUserName")))
回答by Nagy Attila
With spring 4 this solution mock the formLogin and logout using sessions and not cookies because spring security test not returning cookies.
对于 spring 4,此解决方案使用会话而不是 cookie 模拟 formLogin 和注销,因为 spring 安全测试不返回 cookie。
Because it's not a best practice to inherit tests you can @Autowire this component in your tests and call it's methods.
因为继承测试不是最佳实践,所以您可以在测试中 @Autowire 这个组件并调用它的方法。
With this solution each perform operation on the mockMvc
will be called as authenticated if you called the performLogin
on the end of the test you can call performLogout
.
使用此解决方案mockMvc
,如果您performLogin
在测试结束时调用 ,则对 的每个执行操作都将被称为已验证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));
}
}
Because default LogoutRequestBuilder
doesn't support session we need to create another logout request builder.
因为默认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;
}
}
After calling the performLogin
operation all your request in the test will be automatically performed as logged in user.
调用该performLogin
操作后,您在测试中的所有请求都将自动以登录用户身份执行。
回答by wrapperapps
Yet 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 {
...
}