如何在 Spring Boot 测试中模拟会话关闭/过期?
Posted
技术标签:
【中文标题】如何在 Spring Boot 测试中模拟会话关闭/过期?【英文标题】:How to simulate session closing / expiring in Spring Boot tests? 【发布时间】: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());
);
...但这不起作用。有人可以帮我理解我做错了什么吗?
【问题讨论】:
【参考方案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");
【讨论】:
以上是关于如何在 Spring Boot 测试中模拟会话关闭/过期?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Spring Boot 中模拟数据库连接以进行测试?
Spring Boot 在关闭时关闭休眠会话 - 在 @Async 方法完成之前
如何确保 SFTP 会话始终在 spring-batch 结束时关闭
Spring Boot 如何处理 Hibernate 会话?