【问题标题】:How do I make client browser stop requesting an expired session id?如何让客户端浏览器停止请求过期的会话 ID?
【发布时间】:2011-04-17 21:19:36
【问题描述】:

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

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

这是我的限制:

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

我已经使用 Java Servlet Filter 实现了重定向。以下是相关代码sn-ps:

@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 不再有效的信号以使客户端将其丢弃?

【问题讨论】:

    标签: java web-applications session servlets redirect


    【解决方案1】:

    如果request.getSession(false) 返回 null,那么您应该创建一个 new 会话。您可以致电request.getSession(true) 来完成此操作。

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

    【讨论】:

    • 我需要用户看到一个简单的页面,告诉他们旧会话已过期并且他们未保存的输入在他们开始新会话之前已经丢失。如果我调用 getSession(true) 那么它不会继续进行新的对话而没有任何迹象表明最后一个对话已被丢弃吗?
    • 不,这将为用户创建一个新会话,servlet 容器将通过向用户发送更新的 JSESSIONID cookie 来响应该会话。您重定向到 SessionExpiredNotification.html 仍然有效。
    • @matt_b 好的,我会做一些改变并按照你的方式尝试
    【解决方案2】:

    这是我使用的解决方案。我不应该在我的过滤器中调用 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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-17
      • 2019-05-16
      • 2019-06-28
      • 1970-01-01
      • 2021-01-02
      • 2011-10-02
      • 1970-01-01
      • 2014-07-24
      相关资源
      最近更新 更多