立即注册 登录
彼岸网 返回首页

天香公主的个人空间 http://www.bian-wang.com/discuz/?10005 [收藏] [复制] [分享] [RSS] txgz999@yahoo.com

日志

分析Discuz系统安装后无法登录UCenter问题

热度 2已有 380 次阅读2016-12-10 01:32 AM |个人分类:Discuz| 管理中心, 管理员, 创始人, 密码, 先来

最近遇到了一个奇怪的Discuz/UCenter问题。我在服务商namecheap提供的测试网站上全新安装的Discuz 3.2 系统里登录管理中心后,点击最后一个tab来登录与Discuz一同安装的UCenter出现问题,几乎每次填写了UCenter创始人的密码(就是在安装Discuz设的管理员密码)后点击登录后又回到了该登录页。但如果不断的重复登录的话,多试几次也会有一次成功,但是进去后点击网页上任何链接后就又被推回了登录页。而在我自己本地机器上安装的Discuz/UCenter里则从无这类问题。这是怎么回事呢?

让我们先来查下有关代码。在填写了UCenter创始人密码点击登录后,向服务器发送的请求是 admin.php?m=user&a=login

最终检查密码正确性的代码在文件 uc_server/control/admin/user.php 里的函数 control.onlogin 中,其中关键部分是在确认了用户提供的密码是正确的后从用户名(如果是创始人的话,用户名自动定为UCenterAdministrator)产生了一个 sid(估计意为 session id): $this->view->sid = $this->sid_encode($admin['username']); ... $this->setcookie('sid', $this->view->sid, 86400); ... header('location: admin.php?m=frame&a=main&iframe=1'.($this->cookie_status ? '' : '&sid='.$this->view->sid)); exit; 这些代码表明 sid 被同时放入了 query string 和 cookie 中。以图中的数据为例:
query string 中的 sid: f70blEiCdTNvsQpCEXhME6PPP9dSXxCg5W9kFeISvc9K11%2FjGmcOPPyrtfYymtqKVMXuBBlsOi5GNw
cookie 中的 sid: f70blEiCdTNvsQpCEXhME6PPP9dSXxCg5W9kFeISvc9K11%252FjGmcOPPyrtfYymtqKVMXuBBlsOi5GNw
两者有个细微的差别,前者有的 % 在后者里变成了 %25。其原因是代码用的设置的办法是基于PHP的 setcookie 函数,它自动将提供的内容做了一次 url 编码(链接):
Note that the value portion of the cookie will automatically be urlencoded when you send the cookie, and when it is received, it is automatically decoded and assigned to a variable by the same name as the cookie name. If you don't want this, you can use setrawcookie() instead if you are using PHP 5.
当用户登录成功后,服务器端的代码向客户端发送了一个redirect指令。这样客户端接受了cookie后向服务器端发了一个query string和cookie中都带着sid(但有上面提到的细微差别)的请求 admin.php?m=frame&a=main&iframe=1&sid=..., 在服务器收到这个请求后会检查发送过来的sid从中找到用户名。但在这一步上Discuz出了问题,没能找到用户名。问题出来哪里呢?让我们看一下相关的代码,在文件 uc_server/model/admin.php里的adminbase类中: function adminbase() { parent::__construct(); $this->cookie_status = 0; $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R')); $this->sid = $this->view->sid = $this->sid_decode($sid) ? $sid : ''; ... } 其中函数 getgpc 的定义是 function getgpc($k, $t='R') { switch($t) { case 'P': $var = &$_POST; break; case 'G': $var = &$_GET; break; case 'C': $var = &$_COOKIE; break; case 'R': $var = &$_REQUEST; break; } return isset($var[$k]) ? (is_array($var[$k]) ? $var[$k] : trim($var[$k])) : NULL; } 函数 adminbase 中的第三句说的是如果变量cookie_status的值非零,则从cookie (即 $_COOKIE)里得到sid,不然就从请求(即 $_REQUEST)里经一次 url 解码后得到sid。由于$_COOKIE在赋值时已经经过一次 url 解码,所以 getgpc('sid', 'C') 给出的是前面产生的sid,以上面的例子为例,就是 f70blEiCdTNvsQpCEXhME6PPP9dSXxCg5W9kFeISvc9K11%2FjGmcOPPyrtfYymtqKVMXuBBlsOi5GNw。那么 rawurlencode(getgpc('sid', 'R')) 是不是也给出同样的值呢?这取决于getgpc('sid', 'R') (即 $_REQUEST["sid"])是不是 f70blEiCdTNvsQpCEXhME6PPP9dSXxCg5W9kFeISvc9K11/jGmcOPPyrtfYymtqKVMXuBBlsOi5GNw。由于 $_REQUEST 在赋值时也经过一次 url 解码,所以 $_REQUEST 如果从 query string 里取值的话就能得到预期的结果,但是如果从 cookie 里取值的话就会得到错误的值 f70blEiCdTNvsQpCEXhME6PPP9dSXxCg5W9kFeISvc9K11%2FjGmcOPPyrtfYymtqKVMXuBBlsOi5GNw。 经检查发现在我的namecheap服务器上总是从cookie取得,而在我本地机器上总是从query string取得。这就是在服务器上出现问题的原因。它导致了后面使用sid_decode是得不到用户名,从而将登录用户重新带回了登录页。那么为什么在不同机器上有不同行为呢?或者说 $_REQUEST 在 cookie 和 query string 有同名变量时,应该从何者取值呢?从PHP手册 (链接) 看是这样说的: 这个次序取决于PHP设置文件 php.ini 里 variables_order 的值,如果这个值没定义的话,则取决于另一个变量 request_order 的值。
request_order string: This directive describes the order in which PHP registers GET, POST and Cookie variables into the _REQUEST array. Registration is done from left to right, newer values override older values. If this directive is not set, variables_order is used for $_REQUEST contents.
检查我的本地机器上的设置发现 request_order 是GP,所以 $_REQUEST 从 query string (即G)得到了sid。而检查我的namecheap服务器的设置发现 request_order无定义而 variables_order 的值是EGPCS,所以 $_REQUEST 从 cookie (即C)得到了sid。 故而解决办法是将前述那段代码里的第三句 $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R')); 改为 $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'G')); 或者更保险点 $sid = $this->cookie_status ? getgpc('sid', 'C') : (getgpc('sid', 'G')?rawurlencode(getgpc('sid', 'G')):getgpc('sid', 'C'));
网上有些帖子建议了另一种解决办法(如链接),将函数 adminbase 里的第二句 $this->cookie_status = 0; 改为 $this->cookie_status = isset($_COOKIE['sid']) ? 1 : 0; (而不改第三句),即将函数 adminbase 改为 function adminbase() { parent::__construct(); $this->cookie_status = isset($_COOKIE['sid']) ? 1 : 0; $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R')); $this->sid = $this->view->sid = $this->sid_decode($sid) ? $sid : ''; ... } 这个代码正是单独下载的UCenter软件里的对应代码。这个改动说的是如果浏览器支持cookie(并且设置项variables_order里含有C)的话就从cookie里取sid,不支持的话就从query string里取sid (而改动前的意思是不管浏览器是否支持cookie,都从 query string 里取sid)。这种解决办法其实是有个前提就是浏览器支持cookie。不然的话还是会遇到我前面指出的问题从而仍然无法登录。所以这种改法还应该将第三句里的 'R' 改成 'G'。

当然一个网站要解决这个问题也可以不改代码而修改PHP设置里前述两个设置项的值。

另外为何用户如果不断尝试登陆最终还是能成功进去,但进去后点击任何链接就又被退回到登陆页?登陆成功是因为那次的 sid 里没有任何经 url 编码会变化的字符,因而那个 bug 就没起作用。如下图

但是登陆成功后并不是一劳永逸,因为下面的代码表明每次发送回复时有会产生新的 sid,而且它赋在了网页上每个链接路经的 query string上,而这个新的 sid 很有可能是含有在 url 编码下会变化的字符,所以点击后由于前述 bug就被推出来了。

发表评论 评论 (4 个评论)

回复 天香公主 2016-12-12 10:07 PM
ladyff: 检查下php有没有安装mbstring的扩展
不解你这个评论和本文的关联。我刚查了在我的namecheap服务器是加载了mbstring的,所以呢?
回复 ladyff 2016-12-12 09:53 PM
检查下php有没有安装mbstring的扩展
回复 天香公主 2016-12-11 01:32 AM
ludi99: 我也听说过这个让人很纠结而且感到无从着手的问题。单单修改 uc_server/model/admin.php第22行 ($this->cookie_status = isset($_COOKIE['sid']) ? 1 : 0;) 还 ...
谢谢点评。感觉这附近的一些代码写得有点毛糙。写入query string和cookie里的sid应该保持完全一致。Url encoding应该是在写redirect的url时加入,而不是放在产生sid的函数sid_encode(及它的反函数sid_decode)的定义里。
回复 ludi99 2016-12-10 08:52 PM
我也听说过这个让人很纠结而且感到无从着手的问题。单单修改 uc_server/model/admin.php第22行 ($this->cookie_status = isset($_COOKIE['sid']) ? 1 : 0;) 还不能完全解决。这下天香找到了故障的根结,很棒!

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 立即注册

小黑屋|Archiver|彼岸网  

Powered by Discuz! X3.1 © 2001-2014 Comsenz Inc.
GMT-4, 2017-3-24 08:02 AM , Processed in 0.057070 second(s), 21 queries.

返回顶部