如何让客户端浏览器停止请求过期的会话 ID? [英] How do I make client browser stop requesting an expired session id?

查看:76
本文介绍了如何让客户端浏览器停止请求过期的会话 ID?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am developing a Java web application that will run on a secure intranet and does not require a user login. The application does, however, keep conversational state in an HttpSession. User input is not persisted to the database until they explicitly click a save button at some stage in the conversation. Until then, their input is retained in the HttpSession object. If their session expires, the user must be directed to a page that informs them of the session expiry.

这工作正常,除了重定向问题.当用户允许其会话闲置的时间超过 中定义的时间时,会话将按预期到期.但是,我尝试将用户重定向到一个简单的您的会话已过期"页面似乎适得其反.重定向工作正常,但除非用户关闭桌面上所有打开的浏览器窗口(不仅仅是那些打开到我的 web 应用程序页面的窗口),否则他们将永远被重定向到会话已过期"页面.

This is working fine except for a problem with the redirect. When a user allows their session to sit idle for longer than the time defined in <session-timeout>, the session expires as expected. However, my attempt to redirect the user to a simple "Your session has expired" page seems to have backfired. The redirect works alright, but unless the user closes all the open browser windows on their desktop (not just the ones that were open to my web app page) they will continue being redirected to the "session expired" page forever.

这是我的限制条件:

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

我已经使用 Java Servlet Filter 实现了重定向.以下是相关代码片段:

I've implemented the redirect with a Java Servlet Filter. Here are the relevant code snippets:

@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 的行为方式完全相同.

The SessionExpiredNotification.html page is meant to be the end of the line. The user should close this browser window and open a new one if they want to start a new conversation. The problem is that the new browser window still wants to use the old session id value that was associated with the now invalidated session whenever the user has any other instances of Internet Explorer open on their desktop. This isn't specific to IE, as I have confirmed that Firefox behaves exactly the same way.

当在我的Filter中到达此代码时:

When this code is reached in my Filter:

String requestedSessionId = httpServletRequest.getRequestedSessionId();
logger.info("requestedSessionId: " + requestedSessionId);

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

I can see that the client-side browser is still holding on to the old session id value and requesting it over and over again.

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

I'm not sure if it is relevant, but my web application container is Tomcat 6.x.

我的问题:
服务器 Web 应用程序如何向客户端工作站发出会话 ID 不再有效的信号,以便客户端将其丢弃?

推荐答案

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

This is the solution I've used. I should not have been calling sendRedirect() in my filter, since that will never return a new JSESSIONID to the client browser. I need to send an actual response that kills the old JSESSIONID Cookie, otherwise the client browser will just keep trying to use it. My first thought was to get the JSESSIONID Cookie from the request header, set it to be expired, then include the expired Cookie in the response so the client will act on the expiry. Other Stackoverflow users suggested that was not a clean solution, so I have scrapped that idea.

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

I replaced my handleSessionExpired() method in the Filter to use a RequestDispatcher. This will allow my Filter to dispatch the request to a custom "your session is expired" JSP page. Unlike a redirect, the RequestDispatcher will send a proper response to the client.

这是我的 Filter 中的主要方法:

Here is the primary method in my 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() 方法非常简单.这个额外的方法调用仅存在于我的过滤器需要处理的另一个特殊用例(但与我的原始问题无关).

My handleSessionExpired() method is very simple. This extra method call only exists because of another special use case that my filter needs to handle (but is not relevant to my original question).

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 页面,因此我使用该属性来决定在页面上显示什么文本.

My sendSessionExpiredResponse() is also quite simple. The call to getSession() will cause a new session to be created (since no valid HttpSession already exists at this point) and the JSESSIONID to be included in the response. That takes care of cleaning the obsolete session id on the client side. I set a request attribute "isExpired" so the session expiry JSP knows to display a message saying that the session is expired. I'm also using the same JSP page when a user manually ends a session, so I use the attribute to decide what text to display on the page.

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:

The getNamedDispatcher() call gets the JSP via my entry in web.xml:

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

这篇关于如何让客户端浏览器停止请求过期的会话 ID?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆