【问题标题】:Automatically perform action in client side when the session expires会话到期时在客户端自动执行操作
【发布时间】:2013-10-06 12:19:49
【问题描述】:

我想在<p:growl> 中显示会话已过期。 我找到了许多处理会话过期的方法,例如Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request,但我无法将面孔消息推送到<p:growl>

当HTTP会话在服务器端自动过期时,如何在客户端自动运行一些(JavaScript)代码?

【问题讨论】:

  • @Xtreme Biker 我试过了<p:poll>

标签: jsf primefaces session-timeout


【解决方案1】:

您可以为此使用 PrimeFaces idle monitor。超时后用户被重定向到注销操作以使会话无效。在显示倒计时对话框前 2 分钟警告用户。再次移动鼠标后会话被延长。

PrimeFaces 空闲监视器和dialog 放置在一个模板中,您可以将其添加到涉及的每个页面:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:p="http://primefaces.org/ui">

<ui:composition>
   <h:form prependId="false">
      <p:idleMonitor
     timeout="#{session.maxInactiveInterval * 1000 - 125000}"
     onidle="startIdleMonitor()"
     onactive="timeoutDialog.hide()" />

      <p:dialog id="timeoutSession"
     header="#{msg['session.expire']}"
     widgetVar="timeoutDialog"
     showEffect="fade" hideEffect="fade"
     modal="true"
     width="400"
     height="110"
     closable="false"
     draggable="false"
     resizable="false"
     appendToBody="true"
     onHide="stopCount()"
     onShow="doTimer()">
     <br />
     <p>
        <span class="ui-icon ui-icon-alert" style="float: left; margin: 8px 8px 0;"/>
        <p:panel>
           #{msg['logoff.soon.1']}
           <span id="dialog-countdown" style="font-weight: bold"></span>
           #{msg['logoff.soon.2']}
        </p:panel>
     </p>
     <br />
     <p style="font-weight: bold;">#{msg['move.cursor']}</p>
      </p:dialog>
      <p:remoteCommand name="keepAlive" actionListener="#{auth.keepSessionAlive}" />
   </h:form>
   <script type="text/javascript">
      var TIME = 120; // in seconds
      var countTimer = TIME;
      var processTimer;
      var timer_is_on = 0;
      var redirectPage = "#{request.contextPath}/auth/j_verinice_timeout";

      var countDownDiv = "dialog-countdown";
      var txtCountDown = null;
      if (!txtCountDown)
        txtCountDown = document.getElementById(countDownDiv);

      function startIdleMonitor() {
        countTimer = TIME;
        txtCountDown.innerHTML = countTimer;
        timeoutDialog.show();
      }
      function timedCount() {
        txtCountDown.innerHTML = countTimer;
        if (countTimer == 0) {
            stopCount();
            window.location.href = redirectPage;
            return;
        }
        countTimer = countTimer - 1;
        processTimer = setTimeout("timedCount()", 1000);
      }
      function doTimer() {
        if (!timer_is_on) {
            timer_is_on = 1;
            timedCount();
        }
      }
      function stopCount() {
        clearTimeout(processTimer);
        timer_is_on = 0;
        keepAlive();
      }
      </script>
</ui:composition>
</html>
  • 第 11 行:空闲监视器的超时时间由系统变量 session.maxInactiveInterval 设置。您在 web.xml 或服务器配置中设置的值。
  • 第 12/13 行:Javascript 方法 startIdleMonitor() 在超时后调用,无需任何用户交互。此方法打开对话框。 timeoutDialog.hide() 在用户再次忙碌时调用:对话框已关闭
  • 第 26/27 行:显示或隐藏对话框时会调用另外两个 Javascript 方法:doTimer() 启动和 stopCount() 停止倒计时。
  • 第 40 行:PrimeFaces 远程命令以保持会话活动。通过调用服务器上的任意方法可以扩展会话。命令由 Javascript 方法 keepAlive() 在第 78 行调用。
  • 第 59-68 行:每秒调用 Javascript 方法 timedCount() 来执行倒计时。在第 63 行完成超时重定向后。

要在多个页面中激活超时处理,请在布局模板中包含超时模板:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xml:lang="de-DE">
<h:head>
  ...
</h:head>
<body>
  <ui:include src="/template/sessionTimeOut.xhtml" />
  <ui:include src="/nav.xhtml"/>>
  <ui:insert name="content">Default content</ui:insert>
  <ui:include src="/footer.xhtml"/>>
</body>
</html>

您可以在 web.xml 中为您的 Web 应用程序设置特定的超时时间:

<!--?xml version="1.0" encoding="UTF-8"?-->
<web-app>
   ...
   <session-config>
      <!-- Session idle timeout in min. -->
      <session-timeout>30</session-timeout>
    </session-config>
</web-app>

您可以在此博客文章中阅读有关此解决方案的更多信息:JSF and PrimeFaces: Session Timeout Handling

【讨论】:

  • 没看懂方法#{auth.keepSessionAlive}
  • keepSessionAlive() 是 JSF bean 中的一个方法。调用此方法会停止应用服务器上用户会话的空闲时间。只需在此方法中调用任意 getter。我认为它甚至可以是空的。
  • 请不要只发布仅链接的答案。如果链接包含答案,请将答案的相关部分放在 StackOverflow 上的答案中。如果链接后面的内容发生变化,或者内容被删除,您的回答将不再回答这个问题。
  • 对于那些使用 Primefaces 4.0 或更高版本的示例,您可能需要使用 Primefaces PF 功能来显示/隐藏对话框,例如onactive="PF('timeoutDialog').hide();" PF('timeoutDialog').show();
  • 我不明白这部分: timeout="#{session.maxInactiveInterval * 1000 - 125000}" 。能详细解释一下吗?
【解决方案2】:

如果您使用的是 JSF 2.3,那么您可以为此使用范围为 &lt;f:websocket&gt; 的会话。当会话在服务器端过期时,&lt;f:websocket&gt; 会自动以close code 1000 ("normal closure") 关闭。

换句话说,就是这样做:

<f:websocket ... scope="session" onclose="sessionScopedSocketCloseListener" />

function sessionScopedSocketCloseListener(code) {
    if (code == 1000) {
        alert("Session has expired!");
    }
}

如有必要,您可以将其与&lt;p:growl&gt; 的 JavaScript API 结合使用。

<p:growl widgetVar="growl" ... />

PF("growl").renderMessage({severity: "warn", summary: "Session has expired!" });

如果您还没有使用 JSF 2.3,那么您始终可以使用 &lt;o:socket&gt;,它提供与 &lt;f:websocket&gt; 完全相同的 onclose 功能。另见How can server push asynchronous changes to a HTML page created by JSF?

【讨论】:

  • 我也有同样的想法,我使用 web socket 来检测会话过期,下面是一个例子&lt;f:websocket channel="notify" scope="session" onopen="console.log('Started !')" onclose="sessionScopedSocketCloseListener"/&gt; 但有一个问题,我有来自客户端的多个会话启动的无限循环,这是一个已知问题?
【解决方案3】:

@uı6ʎɹnɯ lǝıuɐp 发布的代码非常有用,但我会补充一些意见:

  • @Jonathan L 建议将 processTimer = setTimeout("timedCount()", 1000) 替换为 setInterval("timedCount()", 1000)。我认为有道理,但需要一些 修改:

    function doTimer() {
        if (!timer_is_on) {
            timer_is_on = 1;
            processTimer = setInterval("timedCount()", 1000);
        }
    }
    
    function stopCount() {
        clearInterval(processTimer);
        timer_is_on = 0;
        keepAlive();
    }
    

  • timedCount()方法可以改为:

    function timedCount() {
        txtCountDown.innerHTML = countTimer;
        if (countTimer > 0) {
            countTimer = countTimer - 1;
        } else {
            clearInterval(processTimer);
            doLogout();
        }
    }
    

    您需要在 keepAlive 远程命令下添加如下内容:

    <p:remoteCommand name="doLogout" action="#{loginMB.logout()}" 
                     process="@this" partialSubmit="true" immediate="true" />
    

    内部托管 Bean:

    public void logout() {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        validUser = false;
        loggedUser = false;
        redirectToPage("/login.xhtml");
    }
    
    private void redirectToPage(String page) {
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        context.redirect(context.getRequestContextPath() + page);
    }
    

    请记住 p:remoteCommand 的属性 name="doLogout" 已转换为您可以在任何地方调用的 javascript 函数。使用托管 bean 迭代视图是完美的。

    这种方法将防止您的系统堆栈ViewExpiredException


  • 有些人不理解keepAlive这个概念,但是很简单,只要在你的托管bean中添加一个简单的函数即可。示例 keepSessionAlive 操作:

    public void keepSessionAlive () {
        System.out.println(">>> Session is alive... ");
    }
    

    记住名为 keepAlivep:remoteCommand 是一个名为 keepAlive() 的 JavaScript 函数,它调用一个动作 @ 987654336@ 在您的托管 bean 中。

    这个动作表明你没有闲着。


  • idleMonitor 的 session.maxInactiveInterval 引用 web.xml 的 &lt;session-timeout&gt;

    <p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 125000}"
                   onidle="startIdleMonitor()" onactive="timeoutDialog.hide()" />
    

    我建议在会话结束前 5 秒执行会话监视器,以防止在会话结束后运行 doLogout() 并获得 ViewExpiredException

    第一行 javascript 代码是var TIME = 120; // in seconds,这表示结束会话的时间。原始代码# {session.maxInactiveInterval * 1000 - 125000} 将使用会话超时乘以 1.000(因为它是毫秒)并减去 125.000 表示 125 秒,比计数器少 5 秒,因此不需要更改。


我希望这会有所帮助...祝你好运!

【讨论】:

    【解决方案4】:

    我只是想在这里为未来的访问者发帖,我能够找到一个稍微不同但工作的方法,使用 idlemonitor 和 timer 组件。基本上,如果在 web.xml 中将会话超时设置为 30 分钟,则此代码将在用户空闲 28 分钟后打开一个对话框,其中包含以下组件: 消息 - 您即将在几分钟内注销。请单击确定按钮以使会话保持活动状态。 Timer - Primefaces 扩展计时器,将有 2 分钟倒计时。 确定按钮 - 使会话保持活动状态 注销按钮 - 注销用户。 这是此更改的代码:

    sessionTimeOut.xhtml

    <ui:composition xmlns="http://www.w3.org/1999/xhtml"
           xmlns:f="http://java.sun.com/jsf/core"
           xmlns:h="http://java.sun.com/jsf/html"
           xmlns:ui="http://java.sun.com/jsf/facelets"
           xmlns:p="http://primefaces.org/ui"
           xmlns:pu="http://primefaces.org/ultima"
           xmlns:pe="http://primefaces.org/ui/extensions">
    
    
           <h:form>
               <p:idleMonitor timeout="#{session.maxInactiveInterval * 1000 - 130000}" onidle="PF('idleDialog').show();PF('timeoutTimer').start();" />
    
               <p:dialog id="timeoutDialog" header="Are you there?" widgetVar="idleDialog" modal="true" closable="false" draggable="false" resizable="false" >
                    <p:panelGrid columns="1" styleClass="ui-noborder">
    
                        <p:panel>
                            <h:outputText value="You are about to be logged off in " />
                            <p:spacer width="10"/>
                            <pe:timer id="timeoutTimer" widgetVar="timeoutTimer" singleRun="true" timeout="120" format="mm:ss" autoStart="false" listener="#{userSessionBean.logout()}"/>
                            <p:spacer width="10"/>
                            <h:outputText value=" mins." />
    
                        </p:panel>
    
                        <p:panel>
                            <h:outputText value="Please click 'Ok' to keep the session alive" />
                        </p:panel>
    
                        <p:panel style="text-align: center;">
                            <p:commandButton id="confirm" value="Ok" actionListener="#{userSessionBean.keepAlive()}" onclick="PF('timeoutTimer').stop(true);" oncomplete="PF('idleDialog').hide();" process="@this"/>
                            <p:spacer width="10"/>
                            <p:commandButton id="timeoutLogout" value="Log Out" widgetVar="timeoutLogoutWV"
                                   actionListener="#{userSessionBean.logout()}" oncomplete="PF('timeoutTimer').stop(true); PF('idleDialog').hide(); location.reload(true);"/>
                        </p:panel>
    
                    </p:panelGrid>
               </p:dialog>
           </h:form>
    
    </ui:composition>
    

    将其插入到您的基本模板中

    <ui:include src="./sessionTimeOut.xhtml" />
    

    这里是java端代码

    public void keepAlive() {
        logger.info("User " + loggedInUser.getUserLogin() + " requested the Session " + getCurrentHttpSessionId() + "  to be kept alive at " + new Date().toString());
    
        /**
         * Do nothing
         */
    }
    
    
    public String logout() throws IOException {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        externalContext.getContextName();
    
        logger.info(FacesContext.getCurrentInstance().getExternalContext().getContextName());
    
        facesContext.getExternalContext().redirect("/Project/views/login.xhtml");
    
        logger.info("Logout");
    
    }
    

    我希望这会有所帮助。 谢谢。

    【讨论】:

    • 4 次相同的答案?
    猜你喜欢
    • 1970-01-01
    • 2020-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-17
    • 1970-01-01
    相关资源
    最近更新 更多