【问题标题】:Java Servlet HttpSession not maintained on Weblogic session forwardJava Servlet HttpSession 未在 Weblogic 会话转发上维护
【发布时间】:2015-08-13 20:48:55
【问题描述】:

我在这里问这个问题已经走了很长一段路,但我有点没有选择,所以我想我会转而参考这个网站,它似乎总能解决我的大多数问题。很抱歉这篇文章很长,但一些背景很重要。

我们有一个 Java Web 应用程序 (.war),它可以托管在多个应用程序服务器上(Tomcat 用于内部开发,Weblogic 10.3.5 用于某些客户,Websphere 用于其他客户,Glassfish 也用于其他客户)。对于它的价值,它在 jdk 1.6 上

我们使用旧版本的 struts (1.0.2) 进行操作映射,在 web.xml 文件中定义了几个过滤器,我们还为一些应用程序服务器提供了额外的配置文件(如 weblogic.xml weblogic 的文件,其中包含很少的元素)。视图层都是jsp加一些js。

我们有一个主入口点/类来处理扩展了 Struts ActionServlet 类的 http 请求。

这是我在 weblogic (WLS) 服务器 10.3.5 上部署和测试应用程序时遇到的问题,我要描述的问题从未在任何其他应用程序服务器上遇到过。

用户将通过调用初始 struts 操作(如“/login”)来尝试简单的初始日志记录操作。

<action path="/login"
        type="com.<...>.LoginAction"
        name="loginForm"
         scope="session"
        validate="false">
        <forward name="success" path="/completeLogin" internal="true"/>
        [...]
    </action>

应用过滤器,启动 httpsession(检查 request.getSession(false),然后如果 HttpSession 显示为空,则调用 request.getSession(),这在第一个过滤器中是预期的)。

在第一个动作的成功结果之后,我们在内部将请求如上所示转发到下一个 Struts 动作。此操作也成功完成并将请求转发到下一个 struts 操作。

在继续之前,重要的是要注意这两个操作都在 HttpSession 对象中设置了重要属性,这些属性稍后将用于我们的应用程序业务逻辑。

下一个动作映射到一个 .jsp 页面(转换为非内部 ActionForward):

<action path="/completeLogin"
        type="com.<...>.CompleteLoginAction"
        name="loginForm"
        scope="session">            
        <forward name="success" path="home.jsp"/>
        [...]
    </action>

就在 /login 和 /completeLogin 操作完成之前,我打印 HttpSession id 以确保在一次调用中重复使用相同的会话 id。

问题是当我的 ActionServlet 通过 RequestDispatcher#forward(ServletRequest,ServletResponse) 方法分派第三个请求时,第三个操作失败,因为我们希望从 HttpSession 中检索一些属性(之前已成功设置)但不存在,因为令人惊讶的是,生成了一个新的 HttpSession 并将其传递给第三个操作,而不是原始请求的 HttpSession。 (我打印了 id,发现它与前 2 个打印的 id 不同),因为我无法访问这些属性,应用程序然后抛出一个异常并且用户无法使用应用程序,因为他/她根本无法登录。

现在,我在来这里之前尝试了一些事情:

  • 我们有一个实现 HttpSessionListener 接口的类,它会在创建或销毁 HttpSession 时通知我们。在我测试过的所有情况下,我总是看到原始和第二个 http 会话创建的通知,但我从未收到会话破坏的通知。我假设 weblogic 必须在某个地方拥有原始会话并且永远不会破坏它,而是创建一个新会话。
  • 尽管如此,我还是确保检查对 HttpSession#invalidate() 方法的调用,并且在上面列出的流程中没有看到在我们的 Filters、ActionServlet 或 Action 类本身中调用了任何方法。
  • RequestDispatcher 类是特定于容器的,即,在检索调度程序实例时,供应商实现实际上是在运行时调用的。我认为 Oracle 的调度程序可能有问题,因为其他供应商都没有问题(并且正在执行相同的代码),所以我向 Oracle 提出了服务请求,并且仍在与他们的一些工程师沟通以找到解决方案,但是尽管我向他们发送了无数解释问题的日志文件,但我很难向他们证明我的问题。
  • 阅读了 Oracle 的一些文档后,我意识到与大多数 servlet 一样,HttpSession 对象与浏览器 Cookie 密切相关。在我们的配置中,我们没有使用任何形式的session persistence,我们也没有创建任何额外的cookie,而只是存储http会话属性。我们的客户还确认在他们的浏览器上启用了 cookie。我还能够在内部在 2 个浏览器上重现该问题。然后我开始研究cookies,这似乎是我现在的主要线索。我检查了在初始过滤器之后是否存在任何 cookie。令我惊讶的是,我原本预计没有 cookie,因为据我了解,为会话创建的默认 cookie 设置为在会话结束后过期(浏览器关闭/离开应用程序)。

所以我继续尝试以下方法,它似乎已经工作了一段时间,但现在用户回来告诉我们,用户仍然不时退出。 更糟糕的是,这个问题并不一致,有时会发生,有时不会。 我写的脏补丁是在最初的 LoginAction 中。我扫描请求对象中的 cookie 并循环查看是否找到任何名称为“JSESSIONID”的 cookie 并检查它们的值。如果它们的值与当前 http 会话的会话 ID 不匹配,我会更新 cookie。它工作了一段时间,但现在问题似乎又回来了。

补丁代码示例:

public class LoginAction extends GenericAction {

private static final Logger log = LoggerFactory.getLogger(LoginAction.class);
private static final String JSESSIONID_COOKIE_NAME = "JSESSIONID";

@Override
public ActionForward internalPerform(ActionMapping mapping, BaseForm form,
        HttpServletRequest request, HttpServletResponse response) throws InvalidSessionException {

    String clientOwner = null;
    String registeredHost = null;

    /*
     * Temporary patch for Weblogic JAS. Will be removed eventually.
     */
    verifyJSessionIdCookies(request, response);

    try {
        [...]
}

补丁方法:

protected void verifyJSessionIdCookies(HttpServletRequest request, HttpServletResponse response) {
        Cookie[] existingCookies = request.getCookies();
        HttpSession session = request.getSession(false);
        if (null != existingCookies && null != session) {
            for (Cookie cookie : existingCookies) {
                if (cookie.getName().equals(JSESSIONID_COOKIE_NAME) && !cookie.getValue().equals(session.getId())) {
                    log.debug("Updating current client JSESSIONID cookie from {} to value {}", cookie.getValue(),
                            session.getId());
                    cookie.setValue(session.getId());
                }
            }
        }
    }
}

我们的 weblogic.xml 文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<weblogic-web-app>
<container-descriptor>
    <prefer-web-inf-classes>false</prefer-web-inf-classes>
    <prefer-application-packages>
        <package-name>org.apache.commons.lang.*</package-name>
    </prefer-application-packages>
</container-descriptor>

我还在 weblogic 管理控制台本身上启用了 HttpDebug 日志,并且在请求转发即将失败时经常看到此消息:

(初始会话创建)

<BEA-000000> <HttpRequest@17756711 - /ourwebapp/login.do: SessionID not found for WASC=ServletContext@2256126[app:ourwebapp module:ourwebapp path:/chapel spec-version:2.5]>
<BEA-000000> <HttpRequest@17756711 - /ourwebapp/login.do: Creating new session> 

[...]

(失败)

<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: [RemoteSessionFetching] obtained workManager: null> 
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Servername: localhost>
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Serverport: 7001>
[..]
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: SessionID not found for WASC=ServletContext@2256126[app:ourwebapp module:ourwebapp path:/ourwebapp spec-version:2.5]> 
<BEA-000000> <HttpRequest@11453786 - /ourwebapp/getHomePage.do: Creating new session>

所以我想我的问题是,以前有没有人遇到过这个问题,底线是在 weblogic 10.3.5 上运行 RequestDispatcher#forward() 之后我的 http 会话不一样?或者同时有什么临时解决方法的想法?

我可能忘记了什么?

我的第三个操作调用 request.getSession(),而不是 request.getSession(false),因为它假定此时已创建,但检索它的属性会显示一个空的地图/列表。

weblogic 中有什么不同之处可能导致这种行为?

ActionServlet 示例:

RequestDispatcher rd = getServletContext().getRequestDispatcher(path);
        if (null == rd) {
            String errorMessage = internal.getMessage(REQUEST_DISPATCHER, path);
            log.debug(errorMessage);
             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, errorMessage);
            return;
        }

        if (null != request.getAttribute(Constants.INCLUDED_REQUEST)) {
            rd.include(request, response);
        } else {
            try {
                //fails after this call
                rd.forward(request, response); [..]

第三个动作:

public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest request,
        HttpServletResponse response) throws IOException, ServletException {
    //displays a new id
    log.debug("Http session after forward : {}", request.getSession(false).getId());
    String mappingPath = mapping.getPath();
    boolean outOfSessionAction = outOfSessionPaths.contains(mappingPath);

    Attribute attr = null;
    if (!outOfSessionAction) {
        attr = request.getSession().getAttribute("attr1");
        if (attr == null) {
            //we should be retrieving this attribute but we fail because 
            //new HttpSession in the request object
            return processException(request, mapping, new BusinessException(true));
        }
    }

【问题讨论】:

  • 对此进行更新,以防有人遇到同样的问题。就我而言,实际上问题似乎在链条上更高。托管在单独的 weblogic 实例上的另一个应用程序在初始登录请求中发送自己的 JSESSIONID cookie,我相信这是混淆 weblogic 的原因。尝试验证这一点,将再次更新问题。此外,最初建议的解决方法也行不通。

标签: java session servlets cookies weblogic-10.x


【解决方案1】:

我自己解决了这个问题,这不是一件容易的事,但事实证明,这涉及到几个因素。首先,代码中仍有一个地方会不必要地使会话无效,我将其删除。 但其次,最有趣的是,我们的客户从不同 WLS 实例上的另一个应用程序访问我们的应用程序,但使用相同的域名。 IE:www.abc.com/customer_app 重定向到 www.abc.com/our_web_app,我了解到这样做会导致浏览器在重定向请求中也包含来自 /customer_app 的所有 cookie。其中还有一个 JSESSIONID cookie(在 /our_web_app 的上下文中不存在)。那就是问题所在。

WLS 第一次看到未知的 JSESSIONID cookie 时反应很好,它只是忽略它(因为它无法在内存中将其映射到任何 http 会话)并为 our_web_app 创建一个新的。问题是这两个 cookie 都设置在 cookie-path '/' 上,这意味着每当我们的应用程序中发生转发时,WLS 有时会读取旧的无意义 JSESSIONID cookie,有时会读取为 our_web_app 创建的正确新 cookie。当它读取旧 cookie 时,它​​会再次无法识别它并创建一个新的 http 会话(产生一个新的 JSESSIONID cookie),从而丢失之前创建的 http 会话(登录后)的任何信息。

一个很好的临时解决方法是在通过我们的应用程序创建的 cookie 上设置更具体的 cookie 路径,这样,它们似乎首先出现在 cookie 列表中,并且总是在任何其他 JSESSIONID cookie 之前被首先考虑。

I.E.在 cookie-path '/' 之前,cookie 列表可能显示为: SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2, JSESSIONID:oldID, JSESSIONID: newCorrectID

为我们的网络应用程序中创建的 cookie 设置 cookie-path '/our_web_app' 后: JSESSIONID:newCorrectID,SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2, JSESSIONID:oldID

理想情况下,这可以通过确保不为两个应用程序使用相同的域或什至更改 our_web_app 的 cookie-name 属性(例如 JSESSIOND -> WEB_APP_SESSION_ID)来预防,但出于技术原因(主要是客户方面,与负载平衡和成本问题有关)对我们来说都不是一个选择。

希望有一天这对某人有所帮助。

【讨论】:

  • 临时补丁也被删除了,因为其他原因无法正常工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 2012-09-22
  • 1970-01-01
  • 2011-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多