近排开发的一个后台系统(域名:admin.test.test.com)在接入公司统一SSO时,遇到了cookie冲突引起的登录循环重定向的bug。
公司有一个统一的SSO登录页面,我们开发的系统是跳转到该页面实现登录验证的。有用户反映登录不了,浏览器提示循环重定向了,查看用户浏览器发现有两个相同名称的session cookie:
PHPSESSID=token1; path=/; expired=/; domain=.test.com
PHPSESSID=token2; path=/; expired=/; domain=.test.test.com
显然这两个cookie的domain因为都是admin.test.test.com的根域名,使浏览器都发送到php后台系统了。第一个是后台系统(admin.test.test.com)写入的,是正确的session id,而第二个是由未知系统写入的。测试打印**$_COOKIE[‘PHPSESSID’]**发现输出是:
token2
很明显php读取到了错误的session id,所以导致系统读取不到保存在session中的登录信息,而误判用户为未登录,之后再把用户跳转回SSO登录页面,跳转回SSO系统后,SSO系统知道用户已登录过,再跳转回系统。。。这样就一直循环下去了=。=
为什么PHP会读取到错误的session id呢?打印全局变量**$_SERVER[‘HTTP_COOKIES’]**出来查看:
PHPSESSID=token2;PHPSESSID=token1
发现PHP是能接收到两个同名COOKIE的,但只取了第一个当真正的session id!!
查看php源码,在文件php_variables.c的220行,有注释出php对同名cookie的处理规则:
https://github.com/php/php-src/blob/e10e151e9b92313a7085272c85bebf6c82017fce/main/php_variables.c #220
/*
* According to rfc2965, more specific paths are listed above the less specific ones.
* If we encounter a duplicate cookie name, we should skip it, since it is not possible
* to have the same (plain text) cookie name for the same path and we should not overwrite
* more specific cookies with the less specific ones.
*/
if (Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) != IS_UNDEF &&
symtable1 == Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]) &&
zend_symtable_str_exists(symtable1, index, index_len)) {
zval_ptr_dtor(&gpc_element);
} else {
gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &gpc_element);
}
意思是当php遇到相同名称的cookie时,只会保留**$_SERVER[‘HTTP_COOKIES’]**中的第一个cookie,而余下同名的会忽略跳过。
很明显,只要浏览器传cookie给php时,把token1的session id放在最前面,这样就能正确判断登录成功了! 那么浏览器传递cookie顺序是如何的呢?参考RFC6265 HTTP State Management Mechanism,可以发现:
4.2.2
In particular,if the Cookie header contains two cookies with the same name (e.g.,
that were set with different Path or Domain attributes), servers
SHOULD NOT rely upon the order in which these cookies appear in the
header.
5.3. Storage Model
2. The user agent SHOULD sort the cookie-list in the following
order:
* Cookies with longer paths are listed before cookies with
shorter paths.
* Among cookies that have equal-length path fields, cookies with
earlier creation-times are listed before cookies with later
creation-times.
NOTE: Not all user agents sort the cookie-list in this order, but
this order reflects common practice when this document was
written, and, historically, there have been servers that
(erroneously) depended on this order.
大概意思就是:
- path越长的说明匹配越精确,顺序越靠前
- 假如path相同,cookie创建时间越早的,顺序越靠前
- 顺序与domain没关系
RFC同时也说明并不是所有的浏览器都遵守这个,并且服务器也不应该依赖于cookie出现的顺序。
最后通过更改保存的session id名称来避免冲突解决问题,不过倒让我加深了cookie的了解。