3

在我的应用程序中,它在 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但这似乎与我想要的几乎相反。

4

2 回答 2

3

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

servlet-3_1-final.pdf 的“7.6 Last Accessed Times”说“当 Servlet 容器首先处理作为会话一部分的请求时,认为会话被访问。”

如果 Tomcat 是 Servlet 容器。

何时Request.getSession()或被getSession(boolean create)调用:参见 Request.java 的第 2955行 和 Session.java 的第 269 行

据说

“/**
* 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.
*/“  

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

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

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

操作:

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

请参阅org.apache.catalina.authenticator.AuthenticatorBase 的第 511 行 参考https://imgur.com/a/QJ3IJxh它显示逻辑为

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

之后对该方法的以下调用将不会调用access(),这可以在何时看到

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

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

解析度

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

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

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

  1. javax.servlet.FitlerServlet上下文过滤链末端的自定义实现
  2. org.springframework.web.filter.GenericFilterBeanSprint 内部过滤器链的过滤器链末端的自定义实现 。
  3. 一个定制的实现 org.springframework.web.servlet.HandlerInterceptor

然后

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

B>javax.servlet.Fitler在 Servlet context Wraper 的过滤器链末端定制的过滤器实现,Request.getSession()getSession(boolean create)具有完全定制的会话生命周期和会话事件实现。

C> Websocket 和 Long Polling 进行实时通信

于 2020-10-10T01:39:38.977 回答
1

只有当会话被 访问时, 访问时间才会更新HttpServletRequest#getSession。所以如果你确保你没有篡改会话,你应该没问题。 更新:基于JavaDoc,以上内容不正确(即使我彻底搜索了源代码,也没有找到任何负责这种行为的代码)。

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


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

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;
        }

    }

}
于 2013-10-04T22:52:01.250 回答