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

2022-09-04 04:53:51

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

在服务器端,我尝试获取会话:

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

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

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

请求2:

isSessionNew:true
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:false
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58 session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58

request2':

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

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

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

亲切的问候,
暴君

其他研究

我想看看我是否可以用HttpSessionListner注册会话创建。通过这种方式,我可以看到何时创建ID为FD0D502635EEB67E3D36203E26CBB59A(在reaquest1和accept2中发送的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});
    }
}  

in 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 以外的方式创建的会话(当 spring 本身开箱即用地创建会话时),要么不是由侦听器注册的,要么是 cookie/jsessionID 来自其他地方。寻找答案以获取更多信息。

其他帮助我解决HttpSession问题的来源
servlet上下文注入控制器
中的并发概述,当您必须使用HttpSession
对象进行同步时,请使用HttpSession对象进行同步(避免这种情况)
使用HttpSession
进行同步的“最佳”方法 一些弹簧参考内容:
会话管理
安全讨论中的会话管理,关于如何在有会话Id时获取会话Id(我上面做了什么):
coderanch讨论
堆栈溢出
帮助我完成监听器自动布线的帖子


答案 1

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

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

这解释了这样一个事实,即当我发送其他两个请求(request1'和request2')时,它们在相应的应用程序上创建了一个会话ID。

在这里了解更多:
在同一台服务器
中部署多个Web应用程序 在什么条件下创建JSESSIONID?

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


答案 2

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

客户端(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

客户端(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客户端。然后,在服务器端,你会得到相同的会话:

Server(JavaEE) GET HttpSession:

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

推荐