我正在开发一个Java Web应用程序,它将在安全的Intranet上运行,不需要用户登录。但是,应用程序会在HttpSession
中保持会话状态。用户输入不会持久保存到数据库,直到他们在对话的某个阶段明确单击保存按钮。在此之前,他们的输入保留在HttpSession
对象中。如果他们的会话到期,则必须将用户定向到通知他们会话到期的页面。
除了重定向问题外,此功能正常。当用户允许其会话空闲时间超过<session-timeout>
中定义的时间时,会话将按预期到期。但是,我尝试将用户重定向到简单的“您的会话已过期”页面似乎已经适得其反。重定向工作正常,但除非用户关闭其桌面上的所有打开的浏览器窗口(不仅仅是那些对我的Web应用程序页面打开的窗口),否则它们将继续被永远重定向到“会话过期”页面。
以下是我的约束:
我使用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不再有效,以便客户端将其丢弃?
答案 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>