为什么getSession()在短时间内远离后续请求中返回相同的会话?

时间:2012-10-22 16:21:48

标签: java jquery spring http-get httpsession

我发送了一次$.getJSON(HTTP GET)请求(使用不同的数据),一个接一个(假设我们有request1和request2)。我可以在FF和Chrome的开发者工具中看到我有相同的cookie:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A标题字段。

在服务器端,我尝试进行会话:

HttpSession session = request.getSession();
boolean isSessionNew = session.isNew();
String sessionId = session.getId();
String cookieFromRequestHeader = request.getHeader("cookie");

如果我为这两个请求打印这些变量,
request1:

  

isSessionNew:真
  cookieFromRequestHeader:JSESSIONID = FD0D502635EEB67E3D36203E26CBB59A
  session.getId():9212B14094AB92D0F7F10EE21F593E52

request2:

  

isSessionNew:真
  cookieFromRequestHeader:JSESSIONID = FD0D502635EEB67E3D36203E26CBB59A
  session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

如您所见,服务器在request.getSession()上明确为request2创建了一个新会话。但为什么会这样呢?它理论上应该是同步的,并为您提供与第一个请求(首先达到此代码)相同的会话。现在,为了确保会话创建已同步,我执行了以下操作:

@Autowired
private ServletContext servletContext;
...
synchronized (servletContext) {
    HttpSession session = request.getSession();
    boolean isSessionNew = session.isNew();
    String sessionId = session.getId();
    String cookieFromRequestHeader = request.getHeader("cookie");
}

我得到了相同的结果。

如果我再次发送相同的请求(让我们说request1'和request2'),我得到,
request1':

  

isSessionNew:假
  cookieFromRequestHeader:JSESSIONID = E8734E413FA3D3FEBD4E38A7BF27BA58   session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

request2':

  

isSessionNew:假
  cookieFromRequestHeader:JSESSIONID = E8734E413FA3D3FEBD4E38A7BF27BA58
  session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

如果您现在仔细观察,会话ID是相同的(在request1'和request2'中),并且是从request2创建的最后一个。有没有办法让我在很短的时间内从多个后续请求中获得相同的会话?

我没有使用任何特殊功能 - 我正在使用Spring的开箱即用会话策略。此外,它看起来像frist 2请求(request1和request2)中的cookie JSESSIONID来自我第一次访问页面时(假设在创建此JSESSIONID时有一个request0发送到服务器)。但它看起来似乎除非你明确调用request.getSession(),后端/服务器将始终为每个响应创建一个新的JSESSIONID并将其发送回客户端。因此,当响应到来之后从客户端发送新请求时,它将具有新的JSESSIONID。看起来Spring开箱即用的会话处理工作不正常。

亲切的问候,

其他研究

我想知道是否可以使用HttpSessionListner注册会话创建。通过这种方式,我可以看到创建了ID为FD0D502635EEB67E3D36203E26CBB59A(在request1和request2中发送的cookie)的会话。而且,使用侦听器(SessionProcessor)的天气我可以通过id将会话存储在地图中,然后通过cookie中的id检索它们(因此我不需要创建另一个会话)。 所以这是代码:

public interface ISessionProcessor extends ISessionRetriever, ISessionPopulator {
}

public interface ISessionRetriever {

    HttpSession getSession(String sessionId);
}

public interface ISessionPopulator {

    HttpSession setSession(String sessionId, HttpSession session);
}

分离这些的原因是因为我只想允许侦听器向地图添加会话,而控制器只能通过request.getSession()创建会话 - 所以始终调用listner的sessionCreated方法(你将在下面看到)。

public class SessionProcessor implements ISessionProcessor {

    private Map<String, HttpSession> sessions = new HashMap<String, HttpSession>();

    @Override
    public HttpSession getSession(String sessionId) {
            return sessions.get(sessionId);
    }

    @Override
    public HttpSession setSession(String sessionId, HttpSession session) {
            return sessions.put(sessionId, session);
    }

}

public class SessionRetrieverHttpSessionListener implements HttpSessionListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SessionRetrieverHttpSessionListener.class);

    @Autowired
    private ISessionPopulator sessionPopulator;

    @Override
    public void sessionCreated(HttpSessionEvent se) {
            HttpSession session = se.getSession();
            LOGGER.debug("Session with id {} created. MaxInactiveInterval: {} session:{}", new Object[]{session.getId(), session.getMaxInactiveInterval(), session});
            sessionPopulator.setSession(session.getId(), session);
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
            HttpSession session = se.getSession();
            // session has been invalidated and all session data (except Id) is no longer available
            LOGGER.debug("Session with id {} destroyed. MaxInactiveInterval: {}, LastAccessedTime: {}, session:{}", 
                            new Object[]{session.getId(), session.getMaxInactiveInterval(), session.getLastAccessedTime(), session});
    }
}  
web.xml中的

:              org.springframework.web.context.ContextLoaderListener     

<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/my-servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<listener>
    <listener-class>mypackage.listener.SessionRetrieverHttpSessionListener</listener-class>
</listener>

<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

在my-servlet-context.xml中:

<bean class="mypackage.listener.SessionProcessor"/>
<bean class="mypackage.SomeController"/>

在我的控制器中:

                    synchronized (servletContext) {
                            String cookieFromRequestHeader = request.getHeader("cookie");
                            LOG.debug("cookieFromRequestHeader:{}", new Object[] {cookieFromRequestHeader});
                            String jsessionIdFromCookieFromRequestHeader = cookieFromRequestHeader.substring(cookieFromRequestHeader.indexOf("=") + 1);
                            LOG.debug("jsessionIdFromCookieFromRequestHeader:{}", new Object[] {jsessionIdFromCookieFromRequestHeader});
                            session = sessionRetriever.getSession(jsessionIdFromCookieFromRequestHeader);
                            LOG.debug("session:{}", new Object[] {session});
                            if (session == null) {
                            LOG.debug("request.isRequestedSessionIdFromCookie():{}, request.isRequestedSessionIdFromURL():{}, WebUtils.getSessionId(request):{}.", new Object[] {request.isRequestedSessionIdFromCookie(), request.isRequestedSessionIdFromURL(), WebUtils.getSessionId(request)});
                            session = request.getSession();
                            boolean isSessionNew = session.isNew();
                            LOG.debug("Is session new? - {}. The session should not be new after the first fingerprint part is received - check if this occured in the logs - if that happend than there is an error!", isSessionNew);
                            LOG.debug("request.isRequestedSessionIdFromCookie():{}, request.isRequestedSessionIdFromURL():{}, WebUtils.getSessionId(request):{}.", new Object[] {request.isRequestedSessionIdFromCookie(), request.isRequestedSessionIdFromURL(), WebUtils.getSessionId(request)});
                            //read https://stackoverflow.com/a/2066883 and think about using ServletContextAware also.
                            LOG.debug("cookieFromRequestHeader:{} session.getId(): {}", new Object[]{cookieFromRequestHeader, session.getId()});
                            }
                    }

这给了我相同的结果。似乎通过除request.getSession以外的方式创建会话(当弹簧本身开箱即创建会话时),或者没有被监听器注册,或者cookie / jsessionID来自其他地方。看看答案更多。

帮助我解决HttpSession问题的其他来源servlet context injection in controller
overview of concurrency when you have to work with HttpSession
using HttpSession object to do synchronization (avoid this)
the "best" way to do synchronization when working with HttpSession
一些春天的参考资料:
session management
session management in security
讨论如何在有sessionId时获取会话(我上面做了什么):
coderanch discussion
stackoverflow
the post that helped me finalize my listener autowiring

3 个答案:

答案 0 :(得分:1)

  

它看起来像frist 2请求的cookie JSESSIONID   (request1和request2)来自我第一次访问该页面   (假设有一个request0在创建时发送到服务器   JSESSIONID)。

事实并非如此。我在同一台服务器上的同一域下部署了2个应用程序。因此,当我调用http://mydomain.com/app1/initpage时,服务器为ID为FD0D502635EEB67E3D36203E26CBB59A的app1创建了一个会话,并将此JSESSIONID以cookie的形式发送给客户端。客户端将cookie保存在mydomain.com下,第二次执行http://mydomain.com/app2/executeService时,客户端浏览器从请求头中的cookie发送了JSESSIONID。我在服务器上收到了它,但这不是其他app2中的会话。

这解释了当我发送另外两个请求(request1'和request2')时,他们在相应的应用程序上创建了一个sessionID。

在这里看一看:
Deploying multiple web apps in same server
Under what conditions is a JSESSIONID created?

至于我的问题的具体答案,似乎您需要使第一个请求同步,因此您始终确保在以下请求中具有相同的会话ID。第一个之后的以下请求可以是异步的。

答案 1 :(得分:0)

只需将您的cookie(使用JESSIONID)存储在客户端中,当您向服务器发送后续请求时,将存储的cookie放入您的请求头字段并发送,然后您将在服务器端获得相同的会话。

CLIENT(IOS)将您的Cookie存储在响应中:

    NSHTTPURLResponse* httpURLReqponse = (NSHTTPURLResponse*) response;
    NSDictionary* allHeaders = [httpURLReqponse allHeaderFields];
    NSLog(@"Response Headers : %@ ", allHeaders);
    NSString* cookie = [allHeaders objectForKey: @"Set-Cookie"];
    DATA.cookies = cookie;      // Store the cookie

CLIENT(IOS)使用cookie发送您的后续请求:

// Put the stored cookie in your request header
[(NSMutableURLRequest*)request addValue: DATA.cookies forHTTPHeaderField:@"cookie"];
[NSURLConnection sendAsynchronousRequest: request queue:[NSOperationQueue mainQueue] completionHandler:nil];

不仅适用于IOS客户端。然后,在服务器端,您将获得相同的会话:

服务器(JavaEE)GET HttpSession:

// Get the session, print its hashCode. You will find out that it's same as previous.
HttpSession session = ServletActionContext.getRequest().getSession();

答案 2 :(得分:0)

我注意到在...web.xml文件中禁用Cookie时会发生这种情况:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">

    <context-root>/some-app</context-root>

    <class-loader delegate="true"/>

    <jsp-config>
        <property name="keepgenerated" value="true">
            <description>Keep a copy of the generated servlet class' java code.</description>
        </property>
    </jsp-config>

    <session-config>
        <session-properties>
            <property name="timeoutSeconds" value="600"/>
            <property name="enableCookies" value="false"/> 
        </session-properties>
    </session-config>

</glassfish-web-app>

应该{​​{1}}保留来自同一连接的会话ID。