11

我想显示<p:growl>会话已过期。我发现了许多处理会话过期的方法,例如JSF/PrimeFaces ajax 请求上的 Session timeout 和 ViewExpiredException 处理,但我无法将 faces 消息推送到<p:growl>.

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

4

4 回答 4

18

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

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

<?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 行:空闲监视器的超时时间由系统 var 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 和 PrimeFaces:会话超时处理

于 2013-10-01T07:32:04.353 回答
13

如果您使用的是 JSF 2.3,那么您可以使用一个范围<f:websocket>为这个的会话。当会话在服务器端过期时,<f:websocket>会自动关闭,关闭代码为 1000(“正常关闭”)

换句话说,只需执行以下操作:

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

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

如有必要,您可以将其与<p:growl>.

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

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

如果您还没有使用 JSF 2.3,那么您始终可以使用<o:socket>它提供onclose<f:websocket>. 另请参阅服务器如何将异步更改推送到由 JSF 创建的 HTML 页面?

于 2017-11-21T08:45:18.063 回答
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();
        }
    }
    

    您需要在keepAliveremoteCommand 下添加如下内容:

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

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

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


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

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

    请记住,p:remoteCommandnamedkeepAlive是一个被调用的 javascript 函数keepAlive(),它调用keepSessionAlive托管 bean 中的一个动作。

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


  • web.xml的session.maxInactiveIntervalidleMonitor 引用<session-timeout>

    <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 秒,因此不需要更改。


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

于 2016-12-09T14:48:21.073 回答
2

我只是想在这里为未来的访问者发帖,我能够找到一种稍微不同但工作的方法,其中包含 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");

}

我希望这有帮助。谢谢。

于 2019-10-18T16:33:04.403 回答