如何让客户端浏览器停止请求过期的会话ID?

时间:2010-09-27 17:00:15

标签: java web-applications session servlets redirect

我正在开发一个Java Web应用程序,它将在安全的Intranet上运行,不需要用户登录。但是,应用程序会在HttpSession中保持会话状态。用户输入不会持久保存到数据库,直到他们在对话的某个阶段明确单击保存按钮。在此之前,他们的输入保留在HttpSession对象中。如果他们的会话到期,则必须将用户定向到通知他们会话到期的页面。

除了重定向问题外,此功能正常。当用户允许其会话空闲时间超过<session-timeout>中定义的时间时,会话将按预期到期。但是,我尝试将用户重定向到简单的“您的会话已过期”页面似乎已经适得其反。重定向工作正常,但除非用户关闭其桌面上的所有打开的浏览器窗口(不仅仅是那些对我的Web应用程序页面打开的窗口),否则它们将继续被永远重定向到“会话过期”页面。

以下是我的约束:

  • 客户端工作站使用Internet Explorer。这是整个公司范围,不会很快改变。
  • 作为正常工作流程的一部分,用户将在桌面上打开多个IE实例。告诉他们关闭IE的所有实例是不可接受的。
  • 此Web应用程序中未使用任何AJAX组件

我使用Java Servlet Filter实现了重定向。以下是相关的代码段:

@Override
public void doFilter(
        ServletRequest request, 
        ServletResponse response,
        FilterChain filterChain) 
        throws IOException, ServletException {
    Validate.notNull(filterConfig);
    Validate.isTrue(request instanceof HttpServletRequest);
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String requestedSessionId = httpServletRequest.getRequestedSessionId();
    logger.info("requestedSessionId: " + requestedSessionId);
    HttpSession httpSession = httpServletRequest.getSession(false);

    if (requestedSessionId == null) {
        // No need to do anything here if no session exists yet
        logger.debug("No session exists yet");
        filterChain.doFilter(request, response);
    } else {
        if (httpSession == null) {
            Validate.isTrue(response instanceof HttpServletResponse);
            HttpServletResponse httpServletResponse =
                (HttpServletResponse) response;
            handleSessionExpired(
                httpServletRequest,
                httpServletResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Session OK | requested URL: " + 
                    httpServletRequest.getRequestURL().toString());
                }
                filterChain.doFilter(request, response);
            }
        }
    }
}

private void handleSessionExpired(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException {
    logger.warn("expired session | id: " + 
        httpServletRequest.getRequestedSessionId());
    String expirationPageURL = 
        httpServletRequest.getContextPath() + "/" + 
        "SessionExpiredNotification.html";
    httpServletResponse.sendRedirect(expirationPageURL);
}

SessionExpiredNotification.html页面意味着该行的结尾。如果用户想要开始新的对话,则应关闭此浏览器窗口并打开一个新窗口。问题是,只要用户在其桌面上打开任何其他Internet Explorer实例,新浏览器窗口仍希望使用与现在无效的会话关联的旧会话ID值。这不是特定于IE,因为我已经确认Firefox的行为完全相同。

Filter

中找到此代码时
String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);

我可以看到客户端浏览器仍然保持旧的会话ID值并一遍又一遍地请求它。

我不确定它是否相关,但我的Web应用程序容器是Tomcat 6.x。

我的问题:
服务器网络应用程序如何通知客户端工作站会话ID不再有效,以便客户端将其丢弃?

2 个答案:

答案 0 :(得分:2)

如果request.getSession(false)返回null,则应创建 new 会话。您可以致电request.getSession(true)

换句话说,在发布的代码中,您没有指示servlet容器创建新会话并将当前请求分配给它。

答案 1 :(得分:0)

这是我用过的解决方案。我不应该在我的过滤器中调用sendRedirect(),因为它永远不会将新的JSESSIONID返回给客户端浏览器。我需要发送一个杀死旧的JSESSIONID Cookie的实际响应,否则客户端浏览器将继续尝试使用它。我的第一个想法是从请求标头获取JSESSIONID Cookie,将其设置为过期,然后在响应中包含过期的Cookie,以便客户端将在到期时执行操作。其他Stackoverflow用户认为这不是一个干净的解决方案,所以我已经废弃了这个想法。

我替换了Filter中的handleSessionExpired()方法以使用RequestDispatcher。这将允许我的Filter将请求分派给自定义的“您的会话已过期”JSP页面。与重定向不同,RequestDispatcher会向客户端发送正确的响应。

以下是我Filter中的主要方法:

@Override
public void doFilter(
        ServletRequest request, 
        ServletResponse response,
        FilterChain filterChain) 
        throws IOException, ServletException {
    Validate.notNull(filterConfig);
    Validate.isTrue(request instanceof HttpServletRequest);
    HttpServletRequest httpServletRequest =
        (HttpServletRequest) request;
    String requestedSessionId = httpServletRequest.getRequestedSessionId();
    logger.info("requestedSessionId: " + requestedSessionId);
    HttpSession httpSession = httpServletRequest.getSession(false);

    if (requestedSessionId == null) {
        // No need to do anything here if no session exists yet
        logger.debug("No session exists yet");
        filterChain.doFilter(request, response);
    } else {
        if (httpSession == null) {
            Validate.isTrue(response instanceof HttpServletResponse);
            HttpServletResponse httpServletResponse =
                (HttpServletResponse) response;
            handleSessionExpired(
                httpServletRequest,
                httpServletResponse);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

我的handleSessionExpired()方法非常简单。这个额外的方法调用只存在,因为我的过滤器需要处理的另一个特殊用例(但与我原来的问题无关)。

private void handleSessionExpired(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException, ServletException {
    logger.info("expired session | id: " + 
        httpServletRequest.getRequestedSessionId());
    sendSessionExpiredResponse(httpServletRequest, httpServletResponse);
}

我的sendSessionExpiredResponse()也很简单。对getSession()的调用将导致创建一个新会话(因为此时尚未存在有效的HttpSession),并且JSESSIONID将包含在响应中。这需要在客户端清理过时的会话ID。我设置了一个请求属性“isExpired”,因此会话到期JSP知道显示一条消息,说明会话已过期。当用户手动结束会话时,我也使用相同的JSP页面,因此我使用该属性来决定在页面上显示的文本。

private void sendSessionExpiredResponse(
        HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse) 
        throws IOException, ServletException {
    httpServletRequest.getSession(true); // force valid session to exist
    httpServletRequest.setAttribute("isExpired", true);
    RequestDispatcher rd = filterConfig.getServletContext()
        .getNamedDispatcher("SessionExpired");
    rd.forward(httpServletRequest, httpServletResponse);
}

getNamedDispatcher()调用通过web.xml中的条目获取JSP:

<servlet>
    <servlet-name>SessionExpired</servlet-name>
    <jsp-file>/SessionExpired.jsp</jsp-file>
</servlet>