【问题标题】:How do I execute an authenticated AJAX request without resetting the tomcat's session timeout?如何在不重置 tomcat 会话超时的情况下执行经过身份验证的 AJAX 请求?
【发布时间】:2011-06-17 13:59:45
【问题描述】:

我有一个现有的 Grails Web 应用程序正在生产中,并且有 30 分钟的会话超时。我们正在运行 Tomcat (tcServer)。

当用户通过身份验证并且在某些页面上,我想向服务器发出一些不延长这 30 分钟会话超时的定期轮询 ajax 请求 - 这样我们的会话超时就不会被阻止。

这个问题类似于this unanswered asp.net question,但没有一个答案可以做到,这在 Java/Tomcat 领域。

如何在不重置 tomcat 会话超时的情况下执行经过身份验证的 AJAX 请求?

是否可以使用某种过滤器或 url 匹配机制来排除请求以延长会话超时?

【问题讨论】:

  • 您可以实现自己的会话管理器(至少我会这样做)扩展 org.apache.catalina.session.StandardManager

标签: java ajax tomcat grails session-timeout


【解决方案1】:

我会使用 Grails 过滤器,它的功能类似于 The-MeLLeR 提出的建议,而无需在所有会话中进行不必要的循环:

class AjaxTimeoutFilters {

   int sessionTimeout = 30 * 60 * 1000
   private static final String TIMEOUT_KEY = 'TIMEOUT_KEY'

   def filters = {
      all(controller:'*', action:'*') {
         before = {
            if (request.xhr) {
               Long lastAccess = session[TIMEOUT_KEY]
               if (lastAccess == null) {
                  // TODO
                  return false
               }
               if (System.currentTimeMillis() - lastAccess > sessionTimeout) {
                  session.invalidate()
                  // TODO - render response to trigger client redirect
                  return false
               }
            }
            else {
               session[TIMEOUT_KEY] = System.currentTimeMillis()
            }

            true
         }
      }
   }
}

会话超时应该是依赖注入或以其他方式与 web.xml 中的值保持同步。

还有两个问题。一种情况是有 Ajax 请求但没有以前的非 Ajax 请求(lastAccess == null)。另一个是在没有非 Ajax 活动 30 分钟后有 Ajax 请求时如何将浏览器重定向到登录页面或您需要去的任何地方。您必须呈现 JSON 或其他一些响应,客户端会检查它是否已超时并执行客户端重定向。

【讨论】:

  • 与其对 sessionTimeout 进行硬编码,session.maxInactiveInterval * 1000 不也可以工作吗?
【解决方案2】:

不,不可能……

一个选项如下:

1) 创建一个 javax.servlet.Filter 并存储会话上最后一次(非 ajax)页面浏览的时间戳。

2) 创建一个 javax.servlet.http.HttpSessionListener 来存储所有活动会话。

3) 使用后台线程使所有过期会话无效。


示例代码:

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LastAccessFilter implements Filter, HttpSessionListener {
    private static final Object SYNC_OBJECT = new Object();
    private static final String LAST_ACCESSED = "lastAccessed";
    private boolean backgroundThreadEnabled;

    public void destroy() {
        synchronized (SYNC_OBJECT){
            backgroundThreadEnabled = false;
            SYNC_OBJECT.notifyAll();
        }
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        if (req instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) req;
            if(!isAjax(httpServletRequest)){
                httpServletRequest.getSession().setAttribute(LAST_ACCESSED, System.currentTimeMillis());
            }
        }
        chain.doFilter(req, resp);
    }
    public static boolean isAjax(request) {
       return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }   
    public void init(FilterConfig config) throws ServletException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (LastAccessFilter.this.backgroundThreadEnabled) {
                    synchronized (SYNC_OBJECT) {
                        try {
                            SYNC_OBJECT.wait(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        if (LastAccessFilter.this.backgroundThreadEnabled) {
                            HttpSession[] sessions;
                            synchronized (activeSessions){
                                sessions = activeSessions.toArray(new HttpSession[activeSessions.size()]);
                            }
                            cleanupInactiveSessions(sessions);
                        }
                    }
                }
            }

            private void cleanupInactiveSessions(HttpSession[] sessions) {
                for (HttpSession session : sessions) {
                    Object lastAccessedObject = session.getAttribute(LAST_ACCESSED);
                    if(lastAccessedObject == null) continue;
                    long lastAccessed = (Long)lastAccessedObject;
                    if(System.currentTimeMillis() > (lastAccessed + 1800000)){//30 Minutes
                        session.invalidate();
                    }
                }
            }
        });

        t.setDaemon(true);
        this.backgroundThreadEnabled = true;
        t.start();
    }

    private final List<HttpSession> activeSessions = new ArrayList<HttpSession>();

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        synchronized (activeSessions) {
            this.activeSessions.add(httpSessionEvent.getSession());
        }
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        synchronized (activeSessions) {
            this.activeSessions.remove(httpSessionEvent.getSession());
        }
    }
}

【讨论】:

  • session.invalidate(); 可能会抛出异常 if (!isValidInternal())... 如果它已经过期(例如),因为后台线程正在处理浅拷贝,它可以有效地与 w 竞争/ 标准会话管理器。另一部分是tomcat回收会话,后台线程可能正在使用回收的版本(并且可以过期!)。
  • 不这么认为...当会话失效(或 Tomcat 将其过期)时,它会通过 HttpSessionListener .sessionDestroyed 方法从会话映射中删除。但是最好添加一个try/catch语句来防止后台线程被杀
猜你喜欢
  • 2013-08-27
  • 2015-11-22
  • 1970-01-01
  • 2021-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-17
相关资源
最近更新 更多