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

35

我有一个处理多部分表单POST请求的servlet。该POST实际上是由嵌入在页面中的Flash文件上传组件发出的。在某些浏览器中,Flash生成的POST不包括JSESSIONID,这使得在POST期间从会话加载某些信息变得不可能。

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

这段代码将所有session以其唯一id为键添加到ServletContext的属性中。我可以在上下文中放置一个sessions的Map,但这似乎是重复的。请发表对此决定的任何想法。接下来,我在我的servlet中添加以下方法以通过id解析session:

    private HttpSession getSession(final String sessionId) {
        final ServletContext context = getServletContext();
        final HttpSession session = (HttpSession) context.getAttribute(sessionId);
        return session;
    }
5个回答

32

没有API可以通过ID检索会话。

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


1
我唯一关心的是人为错误部分,即开发人员使用request.getSession()而不是使用会话ID映射来引入某些代码。我认为“更简单”的解决方案是适当构造URL。 - Taylor Leese
我能想像到 Servlet 請求聽眾會話映射的兩種方式:聽眾是單例,或者將映射放置在 ServletContext 中。 - Robert Campbell
您的监听器将成为单例,而无需在静态变量中保存其实例 - 这仅因为您只会在web.xml中声明它一次。如何在所述监听器和您的servlet之间进行通信取决于您 - 您可以使其成为“真正”的单例;您可以在事件处理期间将其注入会话中,或者您可以使用一些共享空间(由于无法从监听器访问它,因此servlet上下文无法帮助)。 - ChssPly76
使用过滤器来包装每个ServletRequest,用一个检查Flash提供的会话ID的实现替换getSession,并从SessionListener构建的映射中查找会话,如果Flash参数或引用的会话不存在,则推迟到包装请求的实现。 - Andrew Duffy
2
它可以工作,但在集群场景下如何进行会话复制呢?据我所知,Servlet上下文不会被复制。 - Victor Ionescu
显示剩余2条评论

3

一种安全的方式是将jsession id设置在cookie中,这比将其设置在url中更为安全。

一旦它被设置为cookie,那么您可以使用正常的方式检索会话,即使用

request.getSession();

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

3
如果您正在使用Tomcat,可以直接向Tomcat请求(但这很丑陋)。我敢打赌,其他网络服务器也有其他的hacky解决方案。
它使用“管理器”接口的实例来管理会话。让它变得丑陋的是,我没有找到一个好的公共接口来连接,所以我们必须使用反射来获取管理器。
下面是一个上下文监听器,在上下文启动时抓取该管理器,然后可以用于获取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中,然后它应该可以工作。
然后,你可以执行以下操作来获取会话。

1

在Servlet规范中没有办法,但您可以尝试以下方法:

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

  • 或者像Taylor L建议的那样,在URI路径中添加jsessionid参数。

这两种方法都将使您的应用程序与类似Tomcat的Servlet容器绑定;我认为大多数容器都是如此。这两种方法还需要Flash小程序请求页面的cookie,这可能会导致JavaScript依赖性。


0

这是一篇非常好的文章。我看到使用会话监听器来不断向上下文添加会话的一个潜在问题是,根据您拥有的并发会话数量,它可能会变得相当臃肿。还有为监听器进行Web服务器配置的额外工作。

那么对于一个更简单的解决方案怎么样呢?我实现了这个方法,它运行得非常好。因此,在加载Flash上传对象的页面上,将会话和会话ID作为键值对存储在应用程序对象中,然后将该会话ID作为POST参数传递给上传页面。在上传页面上,查看该会话ID是否已经存在于应用程序中,如果是,则使用该会话,否则从请求中获取。另外,然后继续从应用程序中删除该键,以保持一切清洁。

在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);

非常棒的解决方案,大家干得好。谢谢。


6
“quite fat”? 你有测量过吗?你熟悉Java吗?这只是一个参考,不是值的复制... - BalusC
哎呀,你说得对。我没有仔细考虑。不过,这个方法比原来的建议更容易实现。另外,由于我已经完全测试了这段代码,我发现以下问题。如果原帖作者特别指的是SWFUpload,那么当你上传多个文件时,SWFUpload会单独发布每个文件,而不是一次性发布所有文件。因此,在第一次上传后从应用程序对象中删除会话表单后,所有其他上传都失败了。供参考。 - Garfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接