Java Servlet HttpSession 未在 Weblogic 会话转发上维护

Java Servlet HttpSession not maintained on Weblogic session forward

我在这里问这个问题已经走了很长一段路,但我有点不知所措,所以我想我应该转而参考这个网站,它似乎总能解决我的大部分问题。很抱歉 post,但一些上下文很重要。

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

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

我们有一个主入口点/class 来处理扩展 Struts ActionServlet class.

的 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(),这在第一个过滤器)。

在第一个操作的成功结果之后,我们然后 ward 在内部请求如上所示到下一个 Struts 操作。此操作也成功完成,并且 wards 请求下一个 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 不同),因为我无法访问这些属性,应用程序随后抛出异常,用户无法使用该应用程序,因为 he/she 根本无法登录。

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

所以我继续尝试以下方法,它似乎已经工作了一段时间,但现在用户回来告诉我们,用户仍然不时注销。 更糟糕的是,这个问题并不一致,有时会发生,有时不会。 我写的脏补丁是在初始的 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 日志,当 ward 的请求即将失败时经常看到此消息:

(初始会话创建)

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

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

我可能忘记了什么?

我的第 3 个操作调用 request.getSession(),而不是 request.getSession(false),因为假定它已在此时创建,但检索它的属性会显示一个空的 map/list.

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

我自己解决了这个问题,这不是一件容易的事,但事实证明,这涉及到几个因素。首先也是最重要的是,代码中仍有一个地方会不必要地使会话无效,我将其删除。 但其次,也是最有趣的是,我们的应用程序被我们的客户从不同 WLS 实例上的另一个应用程序访问,但使用相同的域名。即: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 都设置在 cookie 路径“/”上,这意味着每当我们的应用程序中发生转发时,WLS 有时会读取旧的无意义的 JSESSIONID cookie,有时会读取为 our_web_app 创建的正确的新 cookie。当它读取旧的 cookie 时,它​​会再次无法识别它并创建一个新的 http 会话(产生一个新的 JSESSIONID cookie),从而丢失先前创建的 http 会话(登录后)的任何信息。

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

I.E.在 cookie 路径 '/' 之前,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 名称 属性(JSESSIOND -> WEB_APP_SESSION_ID 例如)但出于技术原因(主要是在客户端,与负载平衡和成本问题相关)我们都不是一个选择。

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