java - Weblogic session 转发中未维护Java Servlet HttpSession

标签 java session servlets cookies weblogic-10.x

我在这里提出这个问题已经走了很长一段路,并且略有超出选择范围,因此我想我应该转而引用此站点,该站点似乎总是可以解决我的大多数问题。抱歉,很长的帖子,但某些情况很重要。

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

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

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

这是在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看起来为null,则调用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对象与浏览器Cookies紧密相关。在我们的配置中,我们不使用任何形式的session persistence,也不创建任何其他cookie,而仅存储http会话属性。我们的客户还确认在其浏览器上启用了cookie。我还能够在2个浏览器内部重现该问题。然后,我开始研究cookie,这似乎是我目前的主要线索。我检查了初始过滤器之后是否存在任何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));
        }
    }

最佳答案

我本人已经解决了这个问题,这不是一件容易的事,但是事实证明,涉及到几个因素。首先,代码中仍然有一个地方会不必要地使会话无效,我删除了那个地方。
但是其次,也是最有趣的是,我们的应用程序是由客户从另一个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之前。

即在cookie路径“ /”上时,cookie列表可能显示为:
SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2,JSESSIONID:oldID,JSESSIONID:newCorrectID

为在我们的网络应用中创建的cookie设置cookie路径“ / our_web_app”后:
JSESSIONID:newCorrectID,SOME_CUSTOMER_COOKIE1:value1,SOME_CUSTOMER_COOKIE2:value2,JSESSIONID:oldID

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

希望这对某人有帮助。

关于java - Weblogic session 转发中未维护Java Servlet HttpSession,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31998148/

相关文章:

java - 单元测试时支持H2数据库中的DB2功能

jsp - 如何在 Tomcat 6 和 MySQL 中使用连接池?

java - 如何在 servlet 中调用 DAO 方法来提取查询结果

java - 将参数从 jsp 页面 Hook 传递到同一 Hook 中的运行时 portlet

java - GAE JAVA - 通过属性获取对象

java - HashMap 不根据键返回值

php - 尝试编写一个使用 session 变量的函数

java - 如何在使用 Web 服务时获取 session 对象?

php - 如何访问存储在数组 codeigniter 中的 $_$SESSION 数据

java - 在 session 过期之前调用方法