问题
We recently realized that our session cookie is being written out to the fully qualified domain name of our site, www.myapp.com
, for example:
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
We want to switch this to being a cookie that can be shared cross subdomain, so any server at myapp.com
can use the session cookie as well. For example, we'd like our cookie to store like so:
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
We tried just changing our session cookie to use that domain like so:
Cookie sessionCookie = sessionManager.getSessionIdCookie();
sessionCookie.setDomain(".myapp.com");
and this works fine in most cases.
We're finding some users can't login after deploying this new code in some situations. The problem arises when a user:
- has been logged into our site in their current browser session, but aren't currently logged in.
- they try and login again
It appears there are 2 session cookies in their browser:
a stale cookie from their previous session, with the fully qualified domain name
MYAPPCOOKIE: 79D5DB83..., domain: www.myapp.com
the new session cookie for the session they just logged into, with the new domain setting
MYAPPCOOKIE: 79D5DB83..., domain: .myapp.com
What's the best way to manage this old cookie being around? We've tried adding some code to delete the old cookie if a user doesn't have a session, but there are some paths into our app where this doesn't seem to work.
We're open to renaming the cookie if that might work, and are looking for any suggestions others may have. Thanks!
回答1:
The following approach solves the problem by renaming the session cookie to something new, copying the old value to the new cookie name if required.
It uses some Apache modules (mod_headers and mod_setenvif) to copy the old cookie value to the new cookie name when the new cookie doesn't already exist.
# 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=$1 DEBUG_MSG_FOR_REPLACING=TRUE; OLD_SESSION_ID=$1" env=!HAS_NEW_SESSION_ID
</IfModule mod_headers.c>
Note that you can test if the replace is working by looking for the DEBUG_MSG_FOR_REPLACING
cookie value, which I've added for debugging purposes when the replace is done.
The following is some sample code for an endpoint that just dumps the value of the cookies header to the response, which you could use when debugging your Apache changes:
@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;
}
Note that we didn't end up using this solution because of business reasons that stopped us from renaming the cookie. We ended up using this solution instead which allowed us to keep our cookie name the same.
回答2:
This approach works if you're using Apache Shiro, and deletes the old cookie with the fully qualified domain name before creating the new 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);
}
}
To use this, you'd need to set the session manager on your security manager, for example:
// 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);
来源:https://stackoverflow.com/questions/32763252/how-to-safely-change-the-session-cookie-domain-or-name-in-production