如何使用 JSESSIONID 手动加载 Java 会话?

Posted

技术标签:

【中文标题】如何使用 JSESSIONID 手动加载 Java 会话?【英文标题】:How can I manually load a Java session using a JSESSIONID? 【发布时间】:2010-12-02 17:51:11 【问题描述】:

我有一个处理多部分表单帖子的 servlet。该帖子实际上是由嵌入在页面中的 Flash 文件上传组件制作的。在某些浏览器中,Flash 生成的 POST 不包含 JSESSIONID,这使我无法在发布期间从会话中加载某些信息。

Flash 上传组件确实在一个特殊的表单字段中包含 cookie 和会话信息。使用这个表单域,我实际上可以检索 JSESSIONID 值。问题是,我不知道如何使用这个 JSESSIONID 值来手动加载那个特定的会话。

编辑-基于ChssPly76的方案,我创建了如下HttpSessionListener实现:

    @Override
    public void sessionCreated(final HttpSessionEvent se) 
        final HttpSession session = se.getSession();
        final ServletContext context = session.getServletContext();
        context.setAttribute(session.getId(), session);
    

    @Override
    public void sessionDestroyed(final HttpSessionEvent se) 
        final HttpSession session = se.getSession();
        final ServletContext context = session.getServletContext();
        context.removeAttribute(session.getId());
    

这会将所有会话添加到 ServletContext 作为由其唯一 ID 映射的属性。我可以在上下文中放置一个会话地图,但这似乎是多余的。请发表对此决定的任何想法。接下来,我将以下方法添加到我的 servlet 以通过 id 解析会话:

    private HttpSession getSession(final String sessionId) 
        final ServletContext context = getServletContext();
        final HttpSession session = (HttpSession) context.getAttribute(sessionId);
        return session;
    

【问题讨论】:

【参考方案1】:

没有通过 id 检索会话的 API。

但是,您可以做的是在您的 Web 应用程序中实现 session listener 并手动维护由 id 键入的会话映射(会话 id 可通过 session.getId() 检索)。然后,您将能够检索您想要的任何会话(而不是像其他人建议的那样欺骗容器用它替换您当前的会话)

【讨论】:

我唯一关心的是人为错误部分,您将让开发人员使用 request.getSession() 而不是使用会话 ID 的映射来引入一些代码。我相信“更简单”的解决方案是适当地构造 URL。 我可以想象 servlet 向侦听器询问会话映射的两种方式:侦听器是单例,或者将映射放在 ServletContext 中 您的侦听器将是一个单例,您不必在静态变量中保留它的实例——因为您只会在 web.xml 中声明它一次。您如何在所述侦听器和您的 servlet 之间进行通信取决于您 - 您可以使其成为“真正的”单例;您可以在事件处理期间将其注入会话,或者您可以使用一些共享空间(servlet 上下文无济于事,因为它无法从侦听器访问) 如何使用过滤器来包装每个 ServletRequest 并将 getSession 替换为检查 Flash 提供的会话 id、从 SessionListener 构建的映射中查找会话并遵循包装的实现如果 Flash 参数或引用的会话不存在,则请求的实现。 它可以工作,但是集群场景中的会话复制呢?据我所知,servlet 上下文没有被复制。【参考方案2】:

一种安全的方法是在 cookie 中设置 jsession id - 这比在 url 中设置要安全得多。

一旦将其设置为 cookie,您就可以使用

以正常方式检索会话
request.getSession();

method.setRequestHeader("Cookie", "JSESSIONID=88640D6279B80F3E34B9A529D9494E09");

【讨论】:

【参考方案3】:

如果你使用的是Tomcat,你直接问tomcat(但它很难看)。我敢打赌,其他网络服务器还有其他 hacky 解决方案。

它使用“Manager”接口的一个实例来管理会话。丑陋的地方在于我还没有找到一个可以挂钩的漂亮的公共接口,所以我们必须使用反射来获取管理器。

下面是一个上下文监听器,它在上下文启动时抓取该管理器,然后可用于获取 Tomcat 会话。

public class SessionManagerShim implements ServletContextListener 
    static Manager manager;

    @Override
    public void contextInitialized(ServletContextEvent sce) 
        try 
            manager = getManagerFromServletContextEvent(sce);
         catch (NoSuchFieldException | IllegalAccessException e) 
            e.printStackTrace();
        
    

    @Override
    public void contextDestroyed(ServletContextEvent sce) 
        manager = null;
    

    private static Manager getManagerFromServletContextEvent(ServletContextEvent sce) throws NoSuchFieldException, IllegalAccessException 
        // Step one - get the ApplicationContextFacade (Tomcat loves facades)
        ApplicationContextFacade contextFacade = (ApplicationContextFacade)sce.getSource();

        // Step two - get the ApplicationContext the facade wraps
        Field appContextField = ApplicationContextFacade.class.getDeclaredField("context");
        appContextField.setAccessible(true);
        ApplicationContext applicationContext = (ApplicationContext)
                appContextField.get(contextFacade);

        // Step three - get the Context (a tomcat context class) from the facade
        Field contextField = ApplicationContext.class.getDeclaredField("context");
        contextField.setAccessible(true);
        Context context = (Context) contextField.get(applicationContext);

        // Step four - get the Manager. This is the class Tomcat uses to manage sessions
        return context.getManager();
    

    public static Session getSession(String sessionID) throws IOException 
        return manager.findSession(sessionID);
    

您可以在 web.xml 中将其添加为侦听器,它应该可以工作。

然后你可以这样做来获得一个会话。

【讨论】:

【参考方案4】:

servlet 规范中没有办法,但您可以尝试:

在Flash发出的请求中手动设置cookie

或者按照 Taylor L 的建议,在我键入并将 jsessionid 参数添加到 URI 的路径时。

这两种方法都会将您的应用程序绑定到在行为类似于 Tomcat 的 servlet 容器上运行;我想他们中的大多数人都这样做。两者都需要您的 Flash 小程序向页面询问其 cookie,这可能会强加 javascript 依赖项。

【讨论】:

【参考方案5】:

这是一个非常好的帖子。我看到使用会话侦听器不断向上下文添加会话的一个潜在问题是,根据您拥有的并发会话的数量,它可能会变得非常胖。然后是侦听器的 Web 服务器配置的所有额外工作。

那么对于一个更简单的解决方案来说如何呢?我确实实现了这一点,并且效果很好。因此,在加载 flash 上传对象的页面上,将 session 和 sessionid 作为键值对存储在 application 对象中,然后将该 session id 作为 post 参数传递给上传页面。在上传页面上,查看该 sessionid 是否已经在应用程序中,因此使用该会话,否则,从请求中获取一个。另外,然后继续从应用程序中删除该密钥以保持一切清洁。

在 swf 页面上:

application.setAttribute(session.getId(), session);

然后在上传页面:

String sessid = request.getAttribute("JSESSIONID");
HttpSession sess = application.getAttribute(sessid) == null ?
        request.getSession() :
        (HttpSession)application.getAttribute(sessid);
application.removeAttribute(sessid);

非常好的解决方案。谢谢你。

【讨论】:

“相当胖”?你量过吗?你熟悉Java吗?它只是一个参考,而不是价值的副本...... 哎呀,你是对的。没有想通。尽管如此,更容易实施原始建议。另外,由于我现在已经完全测试了这段代码,我发现了以下内容。如果原始发布者专门指 SWFUpload,那么当您上传多个文件时,SWFUpload 将单独发布每个文件,而不是全部发布在一个帖子中。因此,通过在第一次上传后从应用程序对象中删除会话,所有其他的都失败了。仅供参考。

以上是关于如何使用 JSESSIONID 手动加载 Java 会话?的主要内容,如果未能解决你的问题,请参考以下文章

登录后如何刷新 JSESSIONID cookie

Chrome 关闭后 Jsessionid cookie 不会过期

JAVA 修改 JSESSIONID

在 JSESSIONID cookie 中设置 httponly (Java EE 5)

如何记录 tomcat 会话失效或用户注销

JSESSIONID 是如何传递的?作为标头参数还是作为 cookie 参数?