Spring Boot,Spring Security,会话范围 Bean 的会话超时问题,@PreDestroy

Posted

技术标签:

【中文标题】Spring Boot,Spring Security,会话范围 Bean 的会话超时问题,@PreDestroy【英文标题】:Spring Boot, Spring Security, session-timeout issue with session scoped Bean, @PreDestroy 【发布时间】:2015-12-10 18:12:12 【问题描述】:

首先,我需要说我正在使用会话范围的 bean。所以在会话关闭之前,preDestroy() 方法被调用

@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "session")
public class MySessionBean 

    @PreDestroy
    public void preDestroy() 

        //Do Smth with using Security principal

    

当我使用 Spring Security 实用程序注销一切正常时,preDestroy() 方法被调用。

主要问题出现在我使用

server.session-timeout = 60= 1 中的application.properties

    preDestroy() 在会话开始后大约 2.5 分钟内被调用。 更有趣的是SecurityContextHolder.getContext().getAuthentication().getPrincipal();null但我已成功退出。

我也试过了

@Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() 
    return (ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) -> 
        configurableEmbeddedServletContainer.setSessionTimeout(1, TimeUnit.MINUTES);
    

我有同样的结果。 使用Provided Tomcat时也存在问题

更新:

奇怪的是,如果我在 1 分钟后手动检查会话 存在方法preDestroy() 被立即调用。但 Security Principal 已经是null

提前致谢!

【问题讨论】:

没什么奇怪的......如果会话无效,有一个线程每隔 x 秒检查一次。因此,当您将超时设置为 1 分钟时,在实际清除会话之前还有 1 分钟 + 一点。当您自己检查会话时,无效会话已经被清除,然后它被强制检查。 @M.Deinum ,好的,我可以忍受。但是我需要做什么才能在触发方法中拥有Security Principal'alive'?据我了解,服务器会在 1 分钟后终止会话,并使会话无效。所以当@PreDestroy 方法被调用时没有Security Principal left 为什么不为空,会话不再存在,会话中的属性也不再存在。 @M.Deinum ,所以在@PreDestroy 方法中获取Principal 的唯一方法是在没有getter / setter 的情况下创建private field 并在@PostConstruct 方法中实例化它?但主要问题是我调用了安全的方法。我应该让它不安全还是只为这种情况编写一些重复的代码? 不要编写该代码...创建一个ApplicationListener<HttpSessionDestroyedEvent> 以在事件被销毁时获取触发器。这包含会话,还应该包含主体。你这样做的方式总是会导致null,因为当会话超时时,没有调用 Spring Security 过滤器,因此不会设置线程本地。 【参考方案1】:

当会话超时时,SecurityContextHolder.getContext().getAuthentication().getPrincipal()总是返回 nullSecurityContext 仅在请求进入时才会填充,其中一个过滤器会执行此操作。当会话超时时,过滤器当然不会被调用,因此SecurityContext 不会被填充。

改为创建一个实现ApplicationListener<HttpSessionDestroyedEvent> 的bean。 HttpSessionDestroyedEvent 有一个方法 getSecurityContexts,它返回 SecurityContexts,就像原来在 HttpSession 中一样。

public class YourListener implements ApplicationListener<HttpSessionDestroyedEvent> 

    public void onApplicationEvent(HttpSessionDestroyedEvent evt) 
        for (SecurityContext ctx : evt.getSecurityContexts() ) 
             Authentication auth = ctx.getAuthentication();
             Object principal = auth.getPrincipal();
             // Do your thing with the principal.
        
    

【讨论】:

getSecurityContexts 与应用程序监听器和 `session.getAttribute ("SPRING_SECURITY_CONTEXT") 之间是否存在显着差异? 如果由于某种原因决定更改属性名称或存储方式仍然有效,您的解决方案将停止。你也可以在应用程序监听器中使用依赖注入,因为它是一个常规的 spring bean。【参考方案2】:

作为M。 Deinum说:

有一个线程每隔 x 秒检查一次会话是否正常 无效的。因此,当您将超时设置为 1 分钟时,它是 1 分钟 + 在您的会话实际清除之前再多一点。当你检查 自己的会话,无效会话已经被清除 它是强制检查的。

所以preDestroy() 调用的延迟已经解释过了。

接下来的问题是如何在SESSION-TIMEOUT之后得到Security Principal

注意,通过实施

ApplicationListener&lt;HttpSessionDestroyedEvent&gt; HttpSessionListener

Session scope bean

SecurityContextHolder.getContext().getAuthentication() == null 在适当的时候调用 destroy 方法

要获取principal,请访问relatedStackPost 完成后实现HttpSessionListener

@Component
public class MySessionListener implements HttpSessionListener 

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) 
        ...
    

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) 
        HttpSession httpSession = httpSessionEvent.getSession();
        SecurityContext securityContext = (SecurityContextImpl) httpSession.getAttribute("SPRING_SECURITY_CONTEXT");

    

【讨论】:

以上是关于Spring Boot,Spring Security,会话范围 Bean 的会话超时问题,@PreDestroy的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security常用过滤器介绍

Grails Spring Core 安全插件 - 无法解析类

Spring security @secure 不适用于角色层次结构

Spring Boot 学习例子

Spring Boot 2Spring Boot CLI

Spring Security OAuth - 访问此资源需要完全身份验证