场景描述
一个单体应用,启动两个服务,在同一个浏览器访问,登录上一个A服务后,再登录B服务,会导致A的session失效
分析
两个相同服务,彼此应该是没有影响的,但产生了影响,且不同浏览器就没有影响
源码
在一个登录过滤器中,有一段这样的代码,最终是由于session中没有userid导致表象的session失效,但实际上这个session已经不是之前携带userid的session了
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpSession session = httpreq.getSession();
String userid = session.getAttribute("userid")==null?"":(String)session.getAttribute("userid");
if(StringUtils.isEmpty(userid)){
if(this.isAjaxRequest(httpreq)){
httpres.getWriter().print("timeout");
return;
}
}
}
关键在于以下代码
HttpSession session = httpreq.getSession();
查看源码实现
RequestFacade:
@Override
public HttpSession getSession() {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
return getSession(true);
}
@Override
public HttpSession getSession(boolean create) {
if (request == null) {
throw new IllegalStateException(
sm.getString("requestFacade.nullRequest"));
}
if (SecurityUtil.isPackageProtectionEnabled()){
return AccessController.
doPrivileged(new GetSessionPrivilegedAction(create));
} else {
return request.getSession(create);
}
}
Request
@Override
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session == null) {
return null;
}
return session.getSession();
}
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
if (context == null) {
return (null);
}
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return (session);
}
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null) {
manager = context.getManager();
}
if (manager == null)
{
return (null); // Sessions are not supported
}
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return (session);
}
}
// Create a new session if requested and the response is not committed
if (!create) {
return (null);
}
if ((context != null) && (response != null) &&
context.getServletContext().getEffectiveSessionTrackingModes().
contains(SessionTrackingMode.COOKIE) &&
response.getResponse().isCommitted()) {
throw new IllegalStateException
(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Attempt to reuse session id if one was submitted in a cookie
// Do not reuse the session id if it is from a URL, to prevent possible
// phishing attacks
// Use the SSL session ID if one is present.
if (("/".equals(context.getSessionCookiePath())
&& isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
session = manager.createSession(getRequestedSessionId());
} else {
session = manager.createSession(null);
}
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getServletContext().
getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
session.access();
return session;
}
以上有两段关键代码
// 1
if ((session != null) && !session.isValid()) {
session = null;
}
// 2
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
&& getContext().getServletContext().
getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
ApplicationSessionCookieConfig:
public static Cookie createSessionCookie(Context context,
String sessionId, boolean secure) {
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
// NOTE: The priority order for session cookie configuration is:
// 1. Context level configuration
// 2. Values from SessionCookieConfig
// 3. Defaults
// 主要看这段,创建cookie,并将sessionId存储到cookie中
Cookie cookie = new Cookie(
SessionConfig.getSessionCookieName(context), sessionId);
SessionConfig:
/**
* Determine the name to use for the session cookie for the provided
* context.
* @param context
*/
public static String getSessionCookieName(Context context) {
String result = getConfiguredSessionCookieName(context);
if (result == null) {
result = DEFAULT_SESSION_COOKIE_NAME;
}
return result;
}
private static String getConfiguredSessionCookieName(Context context) {
// Priority is:
// 1. Cookie name defined in context
// 2. Cookie name configured for app
// 3. Default defined by spec
if (context != null) {
String cookieName = context.getSessionCookieName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
SessionCookieConfig scc =
context.getServletContext().getSessionCookieConfig();
cookieName = scc.getName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
}
return null;
}
1: 验证session不为空且有效,根据结果(返回新的session)可知,之前的session已经失效,原因继续看
2: 1和2之前生成了新的session,此时的session是新产生的,将此sessionId存储到cookie中,也就是说获取session时应该会去cookie中找sessionId。
那么问题就很清晰了,相同的服务,开启两个,后一个(B服务)产生的session将sessionId存储至cookie中,导致A服务(前一个)的sessionId被覆盖(详见cookie),导致session失效,即1中session失效的原因,于是创建新的session,新的session中没有userid,导致A服务的表象的session失效(这里的表象是此应用的相关业务)。
cookie
根据SessionConfig的代码调试可知,本应用从context中获得到了cookieName为null,最终使用的是默认name:JSESSIONID,Cookie是面向路径的, 在一个目录页面里设置的 Cookie 在另一个目录的页面里是看不到的 ,但这两个服务都是在本地启动,域名都为localhost,所以会相互覆盖