【问题标题】:How to safely change the session cookie DOMAIN or name in production?如何在生产中安全地更改会话 cookie 域或名称?
【发布时间】:2015-12-22 04:03:13
【问题描述】:

我们最近意识到我们的会话 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,并且正在寻找其他人可能提出的任何建议。谢谢!

【问题讨论】:

标签: java session-cookies shiro


【解决方案1】:

以下方法通过将会话 cookie 重命名为新名称、如果需要将旧值复制到新 cookie 名称来解决问题。

当新 cookie 不存在时,它使用一些 Apache 模块(mod_headersmod_setenvif)将旧 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=$1 DEBUG_MSG_FOR_REPLACING=TRUE; OLD_SESSION_ID=$1" env=!HAS_NEW_SESSION_ID
  </IfModule mod_headers.c>

请注意,您可以通过查找 DEBUG_MSG_FOR_REPLACING cookie 值来测试替换是否有效,我在替换完成后添加了该值以用于调试目的。

以下是端点的一些示例代码,它只是将 cookie 标头的值转储到响应中,您可以在调试 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 名称相同。

【讨论】:

    【解决方案2】:

    如果您使用的是 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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      • 1970-01-01
      • 2012-09-05
      • 1970-01-01
      • 2020-05-26
      • 2017-12-23
      • 2015-02-05
      相关资源
      最近更新 更多