【问题标题】:How to implement an automatic logout in a web application?如何在 Web 应用程序中实现自动注销?
【发布时间】:2025-12-16 03:45:01
【问题描述】:

我们的 Web 应用程序使用 JBoss 7.1.1 和 Java(JPA2 和 RichFaces4)运行。目前的问题是,如果用户登录但在一段时间内没有在应用程序内执行任何操作(可能是由于会话超时引起的),应用程序就会卡住。然后用户又得重新加载web应用,看起来不太专业。

您能否给我一个提示,如何使用上述技术实现自动注销?

[更新]
我尝试了很多可能性,但没有一个能正常工作。我的想法是实现一个知道会话何时过期的 SessionTimeoutListener:

@Logged
@WebListener
@Named
public class SessionTimeoutListener implements HttpSessionListener
{
    @Inject
    private Logger logger;

    @Override
    public void sessionCreated(HttpSessionEvent event)
    {
        // not used.
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent event)
    {
        logger.info("Session destroyed.");

        ApplicationContext.setAutoLoggedOut(true);
    }
}

这行得通。但是随后出现了所有问题:我无法从中重定向,因为 FacesContext 在那里为空。我无法触发push events,因为我遇到了一些异常,例如 NameNotFoundException 或类似的(我尝试了很多,但似乎触发事件也无法解决这个问题)。然后我在 ApplicationContext.isAutoLoggedOut() 上尝试了 a4j:poll 但这也不起作用,因为如果我执行轮询事件,会话将永远不会过期。我总是走入死胡同。如果我可以从 SessionTimeoutListener 以某种方式重定向,这将是解决方案。

[可能的解决方案]
我现在对在会话过期后单击视图内的任何按钮时执行的注销感到满意。当前的解决方案只是初步的,尚不适用于生产,但此解决方案有效,我将在此基础上进行构建: 我使用上面的 SessionTimeoutListener。此外,我使用了在 SessionTimeoutListener 之后调用的 PhaseListener,因此如果会话到期,将调用 SessionTimeoutListener 并销毁会话,但 SessionTimeoutPhaseListener 仍然具有 FacesContext。所以我可以在那里重定向到注销页面:

public class SessionTimeoutPhaseListener implements PhaseListener
{
    private static final long serialVersionUID = -8603272654541248512L;

    @Override
    public void beforePhase(PhaseEvent event)
    {
        //not used.
    }

    @Override
    public void afterPhase(PhaseEvent event)
    {
        FacesContext facesContext = event.getFacesContext();

        if (ApplicationContext.isAutoLoggedOut())
        {
            ApplicationContext.setAutoLoggedOut(false);

            try
            {
                facesContext.getExternalContext().redirect("./logout.xhtml");
            }
            catch (IOException e)
            {
            }
        }
    }

    @Override
    public PhaseId getPhaseId()
    {
        return PhaseId.RESTORE_VIEW;
    }
}

ApplicationContext 是一个带有@ApplicationScoped 的类,它存储布尔变量,但这必须更改,因为它会影响当前使用该应用程序的每个用户。我想一些“ThreadLocal context”来解决这个问题。我仍然必须区分自动注销和手动注销。在这两种情况下都会调用侦听器。虽然这个解决方案目前有效,但 PhaseListener 中的重定向也很棘手,因为如果我不将“autoLoggedOut”设置为 false,JSF 会一遍又一遍地调用它(这会导致浏览器中的重定向循环错误)。 .. 正如我所说,只是初步的,但使用 PhaseListener 可能是唯一合适的解决方案。

【问题讨论】:

  • 我不明白。会话超时是自动注销。 “用户必须再次加载 Web 应用程序”是什么意思。它应该只是在会话超时后重定向到登录页面。

标签: java jsf jsf-2 richfaces jboss7.x


【解决方案1】:

有两种方法可以做到这一点:

1 使用拉动机制 每隔 5 秒左右,不断询问服务器会话是否有效,如果服务器说是有效,则休眠指定时间段,如果服务器说无效,则向用户显示消息,然后注销。

2 使用推送机制 使用a4j:push,所以在你的服务器中你有一个TimerTask,它每5秒运行一次检查会话是否有效,如果它看到会话有效,休眠,如果无效,向用户发送消息然后注销。

RichFaces4 在线演示似乎无法正常工作,但请查看link

我不确定 RichFaces 是否有类似Primefaces Idle Monitor

另一种解决方案是使用Custom HttpSessionListener

【讨论】:

    【解决方案2】:

    如果您想处理 ViewExpiredException,您可以将以下设置添加到您的 web.xml

    <error-page>
        <exception-type>javax.faces.application.ViewExpiredException</exception-type>
        <location>/login.jsf</location>
    </error-page>
    

    更好的是,使用PhaseListener tio 检查用户是否在会话中仍然可用。

    FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("User")
    

    如果没有 - 导航到登录页面。

    FacesContext.getCurrentInstance().getApplication().getNavigationHandler().handleNavigation(FacesContext.getCurrentInstance(), null, "login");
    

    【讨论】:

    • 谢谢,但至少错误页面的概念对我不起作用。我想这是因为到目前为止我从未见过 ViewExpiredException 抛出。顺便说一句:我必须把它放在 web.xml 中,因为如果我把它放在 faces-config 中,Eclipse 会抱怨这些标签。
    • Web.xml 是。感谢您指出。已编辑答案
    • 使用 PhaseListener 是目前唯一部分可行的解决方案。查看我更新的帖子。
    • 我也有同样的问题, 不起作用
    【解决方案3】:

    你必须在你的 web.xml 中配置会话超时,值以分钟为单位,不能小于 1:

    <session-config>
        <!-- The application will have 15 minutes for session timeout -->
        <session-timeout>15</session-timeout>
    </session-config>
    

    您应该添加一个过滤器来检查会话是否已过期:

    <filter>
        <filter-name>SessionTimeoutFilter</filter-name>
        <filter-class>edu.home.TimeoutFilter</filter-class>
    </filter>
    

    过滤器将如下所示:

    public class TimeoutFilter implements Filter {
        private String startPage = "index.xhtml";
    
        public void init(FilterConfig filterConfig) throws ServletException {}
        public void destroy() {}
    
        public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws IOException, ServletException {
            if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                if (!isValidSession(httpServletRequest)) {
                    String timeoutUrl = httpServletRequest.getContextPath() + "/"
                        + startPage;
                    //redirects to the first page
                    httpServletResponse.sendRedirect(timeoutUrl);
                }
                filterChain.doFilter(request, response);
            }
        }
    
        private boolean isValidSession(HttpServletRequest httpServletRequest) {
            return (httpServletRequest.getRequestedSessionId() != null) &&
                   httpServletRequest.isRequestedSessionIdValid();
        }
    }
    

    【讨论】:

    • 谢谢,我实现了这个,但有时它不起作用,因为在一种情况下,我的应用程序又卡住了,所以我不能再按任何按钮或链接了。奇怪,我想知道为什么这第一次已经奏效了。我尝试将它与 mprabhat 的解决方案结合起来,因为我更喜欢在用户按下任何按钮之前自动重定向。在导致会话超时后,我可能不应该依赖仍在工作的视图功能。
    【解决方案4】:

    既然是网页,你可以使用一些javascript:

    var timeout;
    var timeoutTime = 294000;
    $().ready(function() {
        $(".loading").bind("ajaxSend", function() {
            timeout = setTimeout("TimedOut()", timeoutTime);
        }
    });
    
    function TimedOut() {
        //here I do an async call to test if a user is timed out
        var isTimedOut = yourMethodToTest();
    
        if(isTimedOut) { //do whatever you need to do here;
            alert("session timed out");
        }
        else { //restart the checker
            timeout = setTimeout("TimedOut()",timeoutTime);
        }
    }
    

    【讨论】:

      最近更新 更多