2010-09-27 77 views
1

我正在开发一个Java Web应用程序,它将在安全的Intranet上运行,并且不需要用户登录。然而,该应用程序确实保持HttpSession中的对话状态。用户输入不会持久保存到数据库,除非他们在对话的某个阶段显式单击保存按钮。在此之前,他们的输入被保留在HttpSession对象中。如果他们的会话过期,则用户必须定向到通知他们会话过期的页面。如何让客户端浏览器停止请求过期的会话ID?

这工作正常,除了重定向的问题。当用户允许他们的会话闲置时间超过<session-timeout>中定义的时间时,会话将按预期期满。但是,我尝试将用户重定向到一个简单的“您的会话已过期”页面似乎已经倒闭了。重定向工作正常,但除非用户关闭桌面上的所有打开的浏览器窗口(不仅仅是打开我的Web应用程序页面),否则他们将继续永久重定向到“会话过期”页面。

这里是我的约束:

  • 客户端工作站使用Internet Explorer。这是全公司范围,不会很快改变。
  • 用户将在其桌面上打开多个IE实例作为其正常工作流程的一部分。告诉他们关闭IE的所有实例是不可接受的。
  • 没有使用此网络应用

我实现重定向与Java Servlet中Filter任何AJAX组件。以下是相关的代码片段:

@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.

我的问题:
如何能在服务器Web应用程序的信号在客户端工作站,一个会话ID不再有效,使得客户端将放弃呢?

回答

0

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

我替换了我的handleSessionExpired()方法Filter以使用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()呼叫经我进入获取JSP中web.xml

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

如果request.getSession(false)返回null,则应该创建一个新的会话。您可以拨打request.getSession(true)来完成此操作。

换句话说,您发布的代码中没有任何一点指示servlet容器创建新会话并为其分配当前请求。

+0

我需要用户看到一个简单的页面,告诉他们旧的会话已过期,他们未保存的输入以前已丢失他们开始新的会议。如果我调用getSession(true),那么它不会继续进行新的对话而没有任何迹象表明最后一个被丢弃了? – 2010-09-27 17:30:27

+0

不,这会为用户创建一个新的会话,servlet容器将通过向用户发送更新的JSESSIONID cookie来响应该会话。您重定向到SessionExpiredNotification.html仍然有效。 – 2010-09-27 18:44:22

+0

@matt_b好的,我会做一些改变,并尝试你的方式 – 2010-09-28 13:18:19

相关问题