【问题标题】:How to simulate session closing / expiring in Spring Boot tests?如何在 Spring Boot 测试中模拟会话关闭/过期?
【发布时间】:2018-05-01 20:23:47
【问题描述】:

我想在此处显示的示例中添加几个测试:

https://spring.io/guides/gs/securing-web/

能够验证用户在会话关闭或到期时无法再访问需要身份验证的资源。我想在我的测试中模拟以下两种情况:

a) 用户自愿结束会话(例如关闭浏览器);

b) 会话超时;

我不知道如何使用 MockMvc 重现这些情况。

我设法做到了以下几点:

@Test
public void sessionIsInvalid() throws Exception {
    FormLoginRequestBuilder login = formLogin()
            .user("user")
            .password("password");

    mockMvc.perform(login)
            .andExpect(authenticated())
            .andDo(mvcResult -> {
                MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
                session.invalidate();
                mockMvc.perform(get("/hello")
                        .session(session))
                        .andExpect(status().isFound());
            });
}

...这似乎可行,但我不完全确定 invalidate 在这种情况下的作用以及它是否符合上述条件 a)。

为了模拟会话超时,我已经这样做了:

@Test
public void sessionExpires() throws Exception {
    FormLoginRequestBuilder login = formLogin()
            .user("user")
            .password("password");

    mockMvc.perform(login)
            .andExpect(authenticated())
            .andDo(mvcResult -> {
                MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
                session.setMaxInactiveInterval(1);
                Thread.sleep(3);
                mockMvc.perform(get("/hello")
                        .session(session))
                        .andExpect(status().isFound());
            });
}

...但这不起作用。有人可以帮我理解我做错了什么吗?

【问题讨论】:

    标签: spring session spring-boot spring-test mockmvc


    【解决方案1】:
    session.setMaxInactiveInterval(1);  // in seconds
    
    Thread.sleep(3);  // in milliseconds
    

    【讨论】:

    • 请在您的回答中添加解释
    【解决方案2】:

    当使用带有 Spring Security 的 Spring Boot 时(这就是你的链接),我的方法是这样的:

    • 创建一个自定义 spring 安全过滤器,它能够“说服”spring security session 已过期(无论它认为 session 是什么)
    • ConcurrentSessionFilter 之前添加自定义过滤器
    • 创建一个内部静态@TestConfiguration 类,理论上它可以配置HttpSecurity 来添加自定义过滤器(这就是我们想要的)。在实践中,我发现通常我必须用 @TestConfiguration 注释类来扩展我的项目的安全配置类(或者至少是主要的,如果有很多,例如 SecurityConfiguration 用于我的项目);因为在SecurityConfiguration 中我通常也声明其他@Bean(例如CorsConfigurationSource)我通常还必须使用@WebMvcTest(properties = "spring.main.allow-bean-definition-overriding=true", ...) 以避免bean 覆盖错误;用@TestConfiguration 注释的类也用@Order(HIGHEST_PRECEDENCE) 注释。
    • 创建一个简单的 web mvc 测试,尝试 GET 一些项目现有的端点,例如:
    @Test
    @SneakyThrows
    @WithMockUser
    void sessionExpired() {
        this.mvc.perform(get("/some-endpoint-here")).andExpect(...);
    }
    
    • 运行测试并期待您配置的会话过期策略启动;见HttpSecurity.sessionManagement(session -> session...expiredUrl(...))HttpSecurity.sessionManagement(session -> session...expiredSessionStrategy(...))

    以下作为 @TestConfiguration 提供的 Spring 安全配置适用于 Spring Boot 2.3.12.RELEASE(可能还有更多)。

    @TestConfiguration
    @Order(HIGHEST_PRECEDENCE)
    static class Config extends SecurityConfiguration {
        public Config(SessionInformationExpiredStrategy expiredSessionStrategy, InvalidSessionStrategy invalidSessionStrategy) {
            super(expiredSessionStrategy, invalidSessionStrategy);
        }
    
        @SneakyThrows
        @Override
        protected void configure(HttpSecurity http) {
            super.configure(http);
            // the custom filter as a lambda expression
            http.addFilterBefore((request, response, chain) -> {
                // preparing some objects we gonna need
                HttpServletRequest httpRequest = (HttpServletRequest) request;
                HttpSession session = httpRequest.getSession(false);
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                // getting our hands on the object that Spring Security believes
                // is the "session" and which is in ConcurrentSessionFilter
                List<Filter> filters = (List) ReflectionTestUtils.getField(chain, "additionalFilters");
                int currentPosition = (int) ReflectionTestUtils.getField(chain, "currentPosition");
                ConcurrentSessionFilter concurrentSessionFilter = (ConcurrentSessionFilter) filters.get(currentPosition);
                SessionRegistry sessionRegistry = (SessionRegistry) ReflectionTestUtils.getField(concurrentSessionFilter, "sessionRegistry");
                // the "session" does not exist (from Spring Security's PoV), 
                // we actually have to create (aka "register") it
                sessionRegistry.registerNewSession(session.getId(), authentication.getPrincipal());
                // the actual session expiration (from Spring Security's PoV)
                sessionRegistry.getSessionInformation(session.getId()).expireNow();
                // let the filters continue their job; ConcurrentSessionFilter
                // follows and it'll determine that the "session" is expired
                chain.doFilter(request, response);
            }, ConcurrentSessionFilter.class);
            log.debug("begin");
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-01-30
      • 2016-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-26
      • 1970-01-01
      相关资源
      最近更新 更多