如何在生产中安全地更改会话 cookie 域或名称?
How to safely change the session cookie DOMAIN or name in production?
我们最近意识到我们的会话 cookie 被写到我们网站的完全限定域名 www.myapp.com
,例如:
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
我们希望将其切换为可以跨子域共享的 cookie,因此 myapp.com
中的任何服务器也可以使用会话 cookie。例如,我们希望我们的 cookie 像这样存储:
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
我们试过只更改我们的会话 cookie 以像这样使用该域:
Cookie sessionCookie = sessionManager.getSessionIdCookie();
sessionCookie.setDomain(".myapp.com");
这在 大多数 情况下工作正常。
我们发现在某些情况下,一些用户在部署此新代码后无法登录。当用户出现问题:
- 已在他们当前的浏览器会话中登录到我们的站点,但当前未登录。
- 他们再次尝试登录
他们的浏览器中似乎有 2 个会话 cookie:
来自他们之前会话的陈旧 cookie,具有完全限定的域名
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
他们刚刚登录的会话的新会话 cookie,具有新的域设置
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
管理这个旧 cookie 的最佳方法是什么?如果用户没有会话,我们已经尝试添加一些代码来删除旧的 cookie,但是在我们应用程序的某些路径中这似乎不起作用。
如果可行,我们愿意重命名 cookie,并且正在寻找其他人可能提出的任何建议。谢谢!
以下方法解决了这个问题,方法是将 session cookie 重命名为新名称,如果需要,将旧值复制到新的 cookie 名称。
它使用一些 Apache 模块 (mod_headers and mod_setenvif) 在新 cookie 不存在时将旧 cookie 值复制到新 cookie 名称。
# We only want to copy the old cookie to the new cookie name
# when it doesnt already exist in the request. To do so, we
# set a "per request" flag which is used to determine if we need
# to do the "cookie copy operation".
<IfModule mod_setenvif.c>
SetEnvIf Cookie "NEW_SESSION_ID=" HAS_NEW_SESSION_ID
</IfModule mod_setenvif.c>
# If the cookie "NEW_SESSION_ID" doesnt exist in the request, copy
# the old cookie value to the new cookie name. This allows seamless
# switching to the new cookie for users with existing sessions, and
# also ensure that if we have to rollback our code change for some
# reason, the old cookie will still be ok. Of course if new sessions
# are created with the new cookie, then they wouldn't be copied over on
# rollback, but a similar approach could be used if someone
# wanted to do so
<IfModule mod_headers.c>
RequestHeader edit Cookie "OLD_SESSION_ID=([0-9a-zA-Z\-]*)" "NEW_SESSION_ID= DEBUG_MSG_FOR_REPLACING=TRUE; OLD_SESSION_ID=" env=!HAS_NEW_SESSION_ID
</IfModule mod_headers.c>
请注意,您可以通过查找 DEBUG_MSG_FOR_REPLACING
cookie 值来测试替换是否有效,我添加该值是为了在替换完成后进行调试。
以下是端点的一些示例代码,它只是将 cookie header 的值转储到响应中,您可以在调试 Apache 更改时使用它:
@GET
@Path("dump_cookies")
public Object dumpCookies(@Context HttpServletRequest request)
{
String result = request.getHeader("Cookie");
String cookies = result.replace("; ", "<br/>");
String html = "<h1>Raw</h1>" + result;
html += "<hr/>";
html += "<h1>Cookies</h1>" + cookies;
return html;
}
请注意,由于业务原因阻止我们重命名 cookie,我们最终并未使用此解决方案。我们最终使用了 this solution instead,这使我们能够保持我们的 cookie 名称相同。
如果您使用的是 Apache Shiro,此方法有效,并在创建新 cookie 之前删除具有完全限定域名的旧 cookie。
ShiroSessionManager.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
public class ShiroSessionManager extends DefaultWebSessionManager
{
@Override
protected void onStart(Session session, SessionContext context)
{
if (isSessionIdCookieEnabled())
{
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
removeSessionCookieForFullDomain(request, response);
}
super.onStart(session, context);
}
private void removeSessionCookieForFullDomain(HttpServletRequest request, HttpServletResponse response)
{
Cookie sessionCookie = getSessionIdCookie();
Cookie cookie = new SimpleCookie(sessionCookie.getName());
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setComment(sessionCookie.getComment());
// Setting the domain to null means use the fully qualified domain name
cookie.setDomain(null);
cookie.setMaxAge(sessionCookie.getMaxAge());
cookie.setPath(sessionCookie.getPath());
cookie.setValue(sessionCookie.getValue());
cookie.setVersion(sessionCookie.getVersion());
cookie.removeFrom(request, response);
}
}
要使用它,您需要在安全管理器上设置会话管理器,例如:
// Create our shiro environment
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
DefaultWebEnvironment environment = new DefaultWebEnvironment();
DefaultWebSessionManager sessionManager = new ShiroSessionManager();
// Use the new session manager
securityManager.setSessionManager(sessionManager);
environment.setWebSecurityManager(securityManager);
SecurityUtils.setSecurityManager(securityManager);
我们最近意识到我们的会话 cookie 被写到我们网站的完全限定域名 www.myapp.com
,例如:
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
我们希望将其切换为可以跨子域共享的 cookie,因此 myapp.com
中的任何服务器也可以使用会话 cookie。例如,我们希望我们的 cookie 像这样存储:
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
我们试过只更改我们的会话 cookie 以像这样使用该域:
Cookie sessionCookie = sessionManager.getSessionIdCookie();
sessionCookie.setDomain(".myapp.com");
这在 大多数 情况下工作正常。
我们发现在某些情况下,一些用户在部署此新代码后无法登录。当用户出现问题:
- 已在他们当前的浏览器会话中登录到我们的站点,但当前未登录。
- 他们再次尝试登录
他们的浏览器中似乎有 2 个会话 cookie:
来自他们之前会话的陈旧 cookie,具有完全限定的域名
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
他们刚刚登录的会话的新会话 cookie,具有新的域设置
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
管理这个旧 cookie 的最佳方法是什么?如果用户没有会话,我们已经尝试添加一些代码来删除旧的 cookie,但是在我们应用程序的某些路径中这似乎不起作用。
如果可行,我们愿意重命名 cookie,并且正在寻找其他人可能提出的任何建议。谢谢!
以下方法解决了这个问题,方法是将 session cookie 重命名为新名称,如果需要,将旧值复制到新的 cookie 名称。
它使用一些 Apache 模块 (mod_headers and mod_setenvif) 在新 cookie 不存在时将旧 cookie 值复制到新 cookie 名称。
# We only want to copy the old cookie to the new cookie name
# when it doesnt already exist in the request. To do so, we
# set a "per request" flag which is used to determine if we need
# to do the "cookie copy operation".
<IfModule mod_setenvif.c>
SetEnvIf Cookie "NEW_SESSION_ID=" HAS_NEW_SESSION_ID
</IfModule mod_setenvif.c>
# If the cookie "NEW_SESSION_ID" doesnt exist in the request, copy
# the old cookie value to the new cookie name. This allows seamless
# switching to the new cookie for users with existing sessions, and
# also ensure that if we have to rollback our code change for some
# reason, the old cookie will still be ok. Of course if new sessions
# are created with the new cookie, then they wouldn't be copied over on
# rollback, but a similar approach could be used if someone
# wanted to do so
<IfModule mod_headers.c>
RequestHeader edit Cookie "OLD_SESSION_ID=([0-9a-zA-Z\-]*)" "NEW_SESSION_ID= DEBUG_MSG_FOR_REPLACING=TRUE; OLD_SESSION_ID=" env=!HAS_NEW_SESSION_ID
</IfModule mod_headers.c>
请注意,您可以通过查找 DEBUG_MSG_FOR_REPLACING
cookie 值来测试替换是否有效,我添加该值是为了在替换完成后进行调试。
以下是端点的一些示例代码,它只是将 cookie header 的值转储到响应中,您可以在调试 Apache 更改时使用它:
@GET
@Path("dump_cookies")
public Object dumpCookies(@Context HttpServletRequest request)
{
String result = request.getHeader("Cookie");
String cookies = result.replace("; ", "<br/>");
String html = "<h1>Raw</h1>" + result;
html += "<hr/>";
html += "<h1>Cookies</h1>" + cookies;
return html;
}
请注意,由于业务原因阻止我们重命名 cookie,我们最终并未使用此解决方案。我们最终使用了 this solution instead,这使我们能够保持我们的 cookie 名称相同。
如果您使用的是 Apache Shiro,此方法有效,并在创建新 cookie 之前删除具有完全限定域名的旧 cookie。
ShiroSessionManager.java
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
public class ShiroSessionManager extends DefaultWebSessionManager
{
@Override
protected void onStart(Session session, SessionContext context)
{
if (isSessionIdCookieEnabled())
{
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
removeSessionCookieForFullDomain(request, response);
}
super.onStart(session, context);
}
private void removeSessionCookieForFullDomain(HttpServletRequest request, HttpServletResponse response)
{
Cookie sessionCookie = getSessionIdCookie();
Cookie cookie = new SimpleCookie(sessionCookie.getName());
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setComment(sessionCookie.getComment());
// Setting the domain to null means use the fully qualified domain name
cookie.setDomain(null);
cookie.setMaxAge(sessionCookie.getMaxAge());
cookie.setPath(sessionCookie.getPath());
cookie.setValue(sessionCookie.getValue());
cookie.setVersion(sessionCookie.getVersion());
cookie.removeFrom(request, response);
}
}
要使用它,您需要在安全管理器上设置会话管理器,例如:
// Create our shiro environment
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
DefaultWebEnvironment environment = new DefaultWebEnvironment();
DefaultWebSessionManager sessionManager = new ShiroSessionManager();
// Use the new session manager
securityManager.setSessionManager(sessionManager);
environment.setWebSecurityManager(securityManager);
SecurityUtils.setSecurityManager(securityManager);