我正在发送一个$.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");
如果我为收到的两个请求打印这些变量,
请求1:
isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId():9212B14094AB92D0F7F10EE21F593E52
请求2:
isSessionNew:true
cookieFromRequestHeader:JSESSIONID=FD0D502635EEB67E3D36203E26CBB59A
session.getId(): E8734E413FA3D3FEBD4E38A7BF27BA58
正如您所看到的,服务器显然在 a 上为 request2 创建了一个新会话request.getSession()
。但它为什么要这样做呢?理论上它应该是同步的,并为您提供与第一个请求(首先到达此代码)创建的相同会话。现在,为了确保会话创建同步,我执行了以下操作:
@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')我得到,
请求1':
isSessionNew:假
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58
session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58
请求2':
isSessionNew:假
cookieFromRequestHeader:JSESSIONID=E8734E413FA3D3FEBD4E38A7BF27BA58
session.getId():E8734E413FA3D3FEBD4E38A7BF27BA58
如果您现在仔细观察,会话 ID 是相同的(在 request1' 和 request2' 中),并且是从 request2 创建的最后一个。有没有办法让我从在很短的时间内到达服务器的多个后续请求中获取相同的会话?
我没有使用任何特殊功能 - 我正在使用 Spring 的开箱即用会话策略。另外,看起来前 2 个请求(request1 和 request2)中的 cookie JSESSIONID 来自我第一次访问该页面(假设在创建此 JSESSIONID 时有一个 request0 发送到服务器)。但看起来除非您显式调用 request.getSession(),否则后端/服务器将始终为每个响应创建一个新的 JSESSIONID 并将其发送回客户端。因此,当响应到来后从客户端发送新请求时,它将有一个新的 JSESSIONID。看起来 Spring 开箱即用的会话处理无法正常工作。
亲切的问候,
despot
额外的研究:
我想看看是否可以使用 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() 创建会话 - 因此侦听器的 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 以外的方式创建会话(当 spring 本身开箱即用地创建会话时),要么没有由侦听器注册,要么 cookie/jsessionID 来自其他地方。查看答案以了解更多信息。
Other sources这帮助我解决了 HttpSession 问题:
控制器中的 servlet 上下文注入 https://stackoverflow.com/questions/2066843/get-web-app-root-from-spring-controller/2066883#2066883
必须使用 HttpSession 时的并发概述 http://www.ibm.com/developerworks/library/j-jtp09238/index.html
使用 HttpSession 对象进行同步(避免这种情况) https://stackoverflow.com/questions/1820629/using-request-getsession-as-a-locking-object
使用 HttpSession 时进行同步的“最佳”方法 https://stackoverflow.com/questions/9802165/is-synchronization-within-an-httpsession-feasible/9802428#9802428
一些春季参考资料:
会话管理 http://static.springsource.org/spring-security/site/docs/3.1.x/reference/session-mgmt.html
安全中的会话管理 http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ns-config.html#Session%20Management
关于当你有 sessionId 时如何获取会话的讨论(我上面所做的):
代码牧场讨论 http://www.coderanch.com/t/365859/Servlets/java/session-object-session-ID
堆栈溢出 https://stackoverflow.com/questions/1499581/how-can-i-manually-load-a-java-session-using-a-jsessionid/1499625#1499625
帮助我完成侦听器自动装配的帖子 http://www.mkyong.com/spring/spring-how-to-do-dependency-injection-in-your-session-listener/