복붙노트

[SPRING] 봄 3.2 새로운 mvc 테스트를 가진 사용자 로그인 방법

SPRING

봄 3.2 새로운 mvc 테스트를 가진 사용자 로그인 방법

로그인 한 사용자가 필요한 서비스를 테스트해야 할 때까지 제대로 작동합니다. 사용자를 컨텍스트에 추가하는 방법 :

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

해결법

  1. ==============================

    1.최신 스프링 보안 테스트 패키지와 함께 MockMVC를 사용하려면 다음 코드를 시도하십시오.

    최신 스프링 보안 테스트 패키지와 함께 MockMVC를 사용하려면 다음 코드를 시도하십시오.

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

    이 기능을 사용하려면 주체 기반 인증을 사용해야합니다.

  2. ==============================

    2.인증에 성공하면 쿠키가 생성되고 그 쿠키 (또는 모든 쿠키 만 포함)를 캡처하여 다음 테스트에서 전달할 수 있습니다.

    인증에 성공하면 쿠키가 생성되고 그 쿠키 (또는 모든 쿠키 만 포함)를 캡처하여 다음 테스트에서 전달할 수 있습니다.

    @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 요청을하지 않는다는 것을 알고 있지만, 위의 통합 테스트와 컨트롤러 및 스프링 보안 구현의 엄격한 분리와 비슷합니다.

    코드를 조금 덜 장황하게 만들기 위해 각 요청을 한 후에 쿠키를 병합 한 다음 각 요청에서 쿠키를 전달합니다.

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

    ... 그리고 쿠키 (...)로 작은 도우미가 적어도 하나의 쿠키가 필요합니다 :

    /**
     * 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);
    
  3. ==============================

    3.보안 컨텍스트에 사용자를 추가 할 수 있어야합니다.

    보안 컨텍스트에 사용자를 추가 할 수 있어야합니다.

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

    4.스프링 4로이 솔루션은 쿠키 보안을 테스트하지 않기 때문에 쿠키를 사용하지 않고 세션을 사용하여 formLogin과 logout을 조롱합니다.

    스프링 4로이 솔루션은 쿠키 보안을 테스트하지 않기 때문에 쿠키를 사용하지 않고 세션을 사용하여 formLogin과 logout을 조롱합니다.

    테스트를 상속하는 것은 모범 사례가 아니므로 테스트에서이 구성 요소를 호출하고 메소드라고 부릅니다.

    이 솔루션을 사용하면 performLogin을 호출 할 수있는 테스트가 끝나면 performLogin을 호출 한 경우 mockMvc에 대한 각각의 작업 수행이 인증 된 것으로 호출됩니다.

    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 작업을 호출 한 후 테스트의 모든 요청은 사용자로 로그인하여 자동으로 수행됩니다.

  5. ==============================

    5.교장과 Somewhy 솔루션은 나를 위해 일하지 않았고, 따라서 다른 방법을 언급하고자합니다.

    교장과 Somewhy 솔루션은 나를 위해 일하지 않았고, 따라서 다른 방법을 언급하고자합니다.

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

    6.또 다른 방법은 ... 다음 주석을 사용합니다.

    또 다른 방법은 ... 다음 주석을 사용합니다.

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

    (출처)

  7. from https://stackoverflow.com/questions/14308341/how-to-login-a-user-with-spring-3-2-new-mvc-testing by cc-by-sa and MIT license