【问题标题】:Control whether session timestamp is updated控制会话时间戳是否更新
【发布时间】:2013-10-04 22:18:48
【问题描述】:

在我的应用程序中,它在 Tomcat 7.0 上使用 Spring Web MVC 运行,我有某些控制器,尽管对它们的请求需要身份验证和有效会话,但我不希望更新会话的到期时间戳。换句话说,我希望会话在没有发生此特定 HTTP 请求时准确地过期。

如果重要的话,这些是 AJAX 方法,虽然我不知道是否重要。

这可以通过通用 Java EE 或一些特殊的 Tomcat 挂钩来完成吗?还有另一种方法可以实现这一目标吗?我知道http://download.oracle.com/javaee/6/api/javax/servlet/http/HttpSession.html#setMaxInactiveInterval%28int%29,但这似乎与我想要的几乎相反。

【问题讨论】:

    标签: java spring tomcat


    【解决方案1】:

    没有。 在 Spring 和您的应用程序有机会处理来自当前用户浏览器的 HTTP 请求之前,tomcat 根据 Servlet 规范更新了当前用户的会话。因此 Spring 和您的应用程序无法控制更新当前用户的会话。

    servlet-3_1-final.pdf 的“7.6 Last Accessed Times”显示“The 当作为会话一部分的请求首先由 Servlet 容器处理时,该会话被视为已访问。"

    如果 Tomcat 是 Servlet 容器。

    Request.getSession()getSession(boolean create) 被调用时: 看 Line 2955 of Request.javaLine 269 of Session.java

    据说

    “/**
    * Update the accessed time information for this session.  This method
    * should be called by the context when a request comes in for a particular
    * session, even if the application does not reference it.
    */“  
    

    相关实现类Line 675 of StandardSession.java 一旦Request.getSession()getSession(boolean create) 被调用,现有会话将通过更新thisAccessedTime 来更新

    共享一个调试过程以查看现有会话的更新位置

    • Tomcat 9.0.38(未部署在集群中)
    • Spring 框架版本 4.3.12.RELEASE
    • Spring-security 版本 4.2.4.RELEASE
    • Servlet 3.1

    操作:

    1. 登录应用程序,所以现在当前用户的会话已经存在。
    2. Line 686 of StandardSession.java 上启用断点
    3. 单击任何 UI 按钮以触发 HTTP 请求。

    Line 511 of org.apache.catalina.authenticator.AuthenticatorBase 参考https://imgur.com/a/QJ3IJxh它显示的逻辑为

    • 通过请求标头 'Cookie: 中的会话 ID 查找会话: JSESSIONID'
    • 通过计算timeNow - thisAccessedTime检查会话是否仍然有效
    • 如果当前用户会话仍然有效,则使用访问时间更新thisAccessedTime。 这是第一个也是唯一一个更新会话的地方

    之后对该方法的下面调用就不会调用access()了,这时可以看到

    继续HTTP请求处理进度,在Line 541 of org.apache.catalina.authenticator.AuthenticatorBase过滤链处理 Line 110 of org.springframework.security.web.context.HttpSessionSecurityContextRepository.javaLine 130 of org.springframework.security.web.session.ConcurrentSessionFilter.javahttps://imgur.com/a/MfjJ2ey

    只有能够控制更新thisAccessedTime的Tomcat才能更新现有会话有效时间。

    分辨率

    其实目的是为了达到一个效果,看起来像一些特殊的HTTP请求(一些特殊的API,URL)不能更新在线用户的会话,结果如果只剩下这些API调用,就没有更多的了其他正常的 API 调用在有效会话间隔内出现,然后使会话无效。

    在选择以下选项之一之前需要根据您的场景进行权衡

    A>找到一个切点,选项可以

    1. javax.servlet.Fitler 的自定义实现在 Servlet 上下文过滤器链的末端
    2. 自定义实现 org.springframework.web.filter.GenericFilterBean 在末尾 Sprint 内部过滤器链的过滤器链。
    3. 自定义实现 org.springframework.web.servlet.HandlerInterceptor

    然后

    • 对于普通 API 调用:记录自定义会话属性 thisAccessedTime
    • 对于特殊的 API 调用:使用会话属性 thisAccessedTime 检查会话是否仍然有效。如果过期,则使会话无效

    B> Servlet上下文过滤器链末端javax.servlet.Fitler的自定义过滤器实现 使用完全自定义的会话生命周期和会话事件实现来包装 Request.getSession()getSession(boolean create)

    C> 用于实时通信的 Websocket 和长轮询

    【讨论】:

    • 感谢您的所有研究。尽管如此,我还是有点困惑。根据您的文章,tomcat 是唯一可以更新 thisAccessedTime 的人,但文章还说,如果您执行 request.getSession(),则当前会话将通过更新 thisAccessedTime 来更新。 request.getSession(通常)在 java 控制器内部调用。此调用是否返回到 tomcat 代码,以便 tomcat 可以更新 thisAccessedTime 并从 java 代码返回到下一行?
    【解决方案2】:

    访问时间在会话为being accessed byHttpServletRequest#getSession 时更新。所以如果你确保你没有篡改会话,你应该没问题。 更新: 基于the JavaDoc 以上是不正确的(即使你我彻底搜索了源代码并且没有找到任何导致此类行为的代码)。

    另一方面,如果您需要在 AJAX 请求中访问会话,那您就大错特错了。我能想到的唯一解决方案是手动存储lastAccessTime(例如在servlet过滤器中),然后检查会话超时和invalidate the session manually(例如在同一个过滤器中)。这应该非常简单且易于实施。


    更新:只是为了好玩,我已经实现了过滤器(未测试):

    public class SessionInvalidationFilter implements Filter {
    
        private static final String LAST_ACCESS_SESSION_ATTR = "lastAccessTime";
    
        private static final long SESSION_TIMEOUT = 1000 * 60 * 20; // 20 minutes
    
        private static final String IGNORE_ACCESS_URI = "/this/will-not/update/access-time";
    
        @Override
        public void init(FilterConfig arg0) throws ServletException {
        }
    
        @Override
        public void destroy() {
        }
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            // Cast to HTTP request and response
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            // Check if we are handling standard request
            if (!IGNORE_ACCESS_URI.equals(httpRequest.getRequestURI())) {
                chain.doFilter(new SessionAccessAwareRequest(httpRequest), response);
                return;
            }
            // Now we can handle the special case of non-tracked request
            boolean expired = false;
            HttpSession session = httpRequest.getSession(false);
            if (session == null) {
                // No session means the AJAX contained no or incorrect JSESSIONID
                expired = true;
            } else {
                Long lastAccessTime = (Long) session.getAttribute(LAST_ACCESS_SESSION_ATTR);
                if (lastAccessTime == null || lastAccessTime + SESSION_TIMEOUT < System.currentTimeMillis()) {
                    session.invalidate(); // Invalidate manually
                    expired = true;
                }
            }
            // Handle error or process normally
            if (expired) {
                httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
            } else {
                chain.doFilter(request, response);
            }
        }
    
        private static class SessionAccessAwareRequest extends HttpServletRequestWrapper {
    
            public SessionAccessAwareRequest(HttpServletRequest request) {
                super(request);
            }
    
            @Override
            public HttpSession getSession() {
                return getSession(true);
            }
    
            @Override
            public HttpSession getSession(boolean create) {
                HttpSession session = super.getSession(create);
                if (session != null) {
                    session.setAttribute(LAST_ACCESS_SESSION_ATTR, System.currentTimeMillis());
                }
                return session;
            }
    
        }
    
    }
    

    【讨论】:

    • 我在HttpSession接口中找不到任何access方法。
    • 顺便说一下,每次向服务器发送请求,剩余会话的过期时间resets(即使使用ajax调用时也是如此)。
    • access 是内部 catalina 方法,它不是来自 servlet 规范(检查我的答案中的 GitHub 链接)。上次 访问时间 正在更新,可能是因为您的应用程序在您不知情的情况下访问了会话(例如 spring 安全性或其他一些组件)。
    • 顺便说一下,我已经在我的答案中添加了示例过滤器实现。
    • 和 Catalina 的 access 方法声明的正确链接 - github.com/apache/tomcat70/blob/trunk/java/org/apache/catalina/… ,其中 JavaDoc 与我的答案的第一段相矛盾......所以你可能是对的,这总是在更新(即使你我确实彻底搜索了源代码)。
    猜你喜欢
    • 2015-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    相关资源
    最近更新 更多