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

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

日志

Discuz系统学习:整合UCenter的应用

热度 4已有 4719 次阅读2016-10-31 11:37 AM |个人分类:Discuz

据UCenter安装说明: "UCenter是一个能沟通多个应用的桥梁,使各应用共享一个用户数据库,实现统一登录,注册,用户管理"。这是个很好的想法。首先众多网站虽然功能各异,但都有注册和管理用户的需要。与其各自开发用户管理功能,不如借用一个第三方开发的公认的用户管理系统,从而把精力集中用在开发网站要提供的核心生意有关的功能上。UCenter就是这样一个系统。其次UCenter还有个特点就是它支持使用它的网站之间能自由的连接起来。在一个网站登陆后,就能跨越到其它网站而不用重新登陆,所以对用户来讲,这些网站无异于一个网站一般。

Discuz就是这样一个用UCenter来管理用户的应用。怎样才能在自己的应用里使用UCenter作为用户管理呢? 最近版的UCenter 1.6 下载文件(链接)里除了UCenter外,还带着一个这样的应用的范例。

下载文件解压后的文件夹upload里包含了可安装的UCenter服务端软件,安装后就成为一个UCenter服务端

下载文件解压后的文件夹uc_client里包含了的UCenter应用端软件,文件夹example里包含了应用范例

将文件夹uc_client拷贝到文件夹example里成为它的子文件夹,再在一个UCenter服务端的应用列表里加入这个应用,并修改它的config.ini.php文件里的内容后,example就成为了一个整合了UCenter的应用
每一个整合UCenter的应用都需要包含UCenter应用端软件uc_client作为它的一个子目录,其中含有一个重要的文件client.php。它还要有一个名叫api 的子目录,包含了一个名叫uc.php的文件,虽然文件名可通过设置来改变,但惯例是叫uc.php。 uc_client提供了两种使用UCenter服务端的办法。一种是如果可能的话直接和UCenter服务端的数据库链接,这样一来UCenter服务端做的很多事,uc_client也能同样处理,这是uc_client里含有很多与UCenter服务端同样的代码的原因。另一种办法是向UCenter服务端发post请求,这种办法应用面更广,应用和UCenter可以在不同的服务器上。client.php可以看成是UCenter服务端驻扎在应用里的代理(proxy), 它隐藏了应用端和服务端通讯的细节,它免除了应用里的代码直接和UCenter服务端联系的需要,应用里的代码只要调用它的函数就能使用UCenter服务端提供的功能。而uc.php则提供给UCenter服务端和应用联系的渠道。一般来讲uc_client包括client.php具有普适性,不同的应用都可以直接用uc_client。而uc.php则对不同的应用需要有所改动。这是因为当UCenter服务端通知它某个事件发生时,uc.php往往要有数据需要保存到应用所用的数据库里,按那里数表的不同,uc.php要做的事也不同。每个这样的应用都要在UCenter服务端里注册,


关键的一点是两者要用一个相同的通讯密钥。当一方发起和对方的联系时,它要将通话内容用这个密钥加密,对方接到加密的内容后再用该密钥解密。这样做的好处是每方都能确认和它通讯的的确是对方。但是回复里的内容一般没有加密,即便其中包含着隐秘信息,如UCenter对应用发送的用户登录请求的回复里包含着用户密码。为何不将回复也用密钥加密呢?所以要确保两者之间联系的安全性,应该给UCenter安装SSL。



下面我们以范例里的用户登陆过程为例来看一下应用是如何和UCenter联系的。


这个流程的关键点有:
1)当用户提交登录资料后,应用是请UCenter来判别该登录资料是否正确。用户向UCenter发送的信息是用只有这个应用和这个UCenter知道的通讯密钥加密的
2)应用接到UCenter对登录资料的确认后,将用户的身份(用户ID和用户名)记录到了cookie里保存在用户的浏览器里。这样以后每次用户通过浏览器和应用联系时,应用都能从它发来的cookie得知用户的身份。注意这个cookie也是用那个通讯密钥加密的,所以别人没法在浏览器里按某用户的ID和用户名来人为添加这样的cookie
3)当应用接到UCenter的确认后,它还启动了同步登录(synlogin) 即让该用户能在同一个机器的同样的浏览器里自动登录到其它用这个UCenter的应用。做法是向UCenter发一个synlogin请求,UCenter回复里包含向其它应用发请求的代码,这样这个浏览器就自动向其它那些应用发了个请求,结果是这些应用也都设置了记载用户身份的cookie。注意这些cookie不是那些应用回复用户直接请求的结果,而是由第三方应用(即这个流程里的第一个应用)提供的网页激发的,所以这是一个跨域设置,一般浏览器是不支持的,除非那些应用的回复的头部加了个p3p设置。

其中一些关键步骤的代码如下:
1. example (a.k.a. my application) 包含了用户登陆界面: examples/code/login_db.php echo '<form method="post" action="'.$_SERVER['PHP_SELF'].'?example=login">'; echo '登录:'; echo '<dl><dt>用户名</dt><dd><input name="username"></dd>'; echo '<dt>密码</dt><dd><input name="password" type="password"></dd></dl>'; echo '<input name="submit" type="submit"> '; echo '</form>'; 和对用户提交登陆资料的处理: examples/code/login_db.php //通过接口判断登录帐号的正确性,返回值为数组 list($uid, $username, $password, $email) = uc_user_login($_POST['username'], $_POST['password']); setcookie('Example_auth', '', -86400); if($uid > 0) { if(!$db->result_first("SELECT count(*) FROM {$tablepre}members WHERE uid='$uid'")) { //判断用户是否存在于用户表,不存在则跳转到激活页面 $auth = rawurlencode(uc_authcode("$username\t".time(), 'ENCODE')); echo '您需要需要激活该帐号,才能进入本应用程序<br>< a href="'.$_SERVER['PHP_SELF'].'?example=register&action=activation&auth='.$auth.'">继续</a>'; exit; } //用户登陆成功,设置 Cookie,加密直接用 uc_authcode 函数,用户使用自己的函数 setcookie('Example_auth', uc_authcode($uid."\t".$username, 'ENCODE')); //生成同步登录的代码 $ucsynlogin = uc_user_synlogin($uid); echo '登录成功'.$ucsynlogin.'<br>< a href="'.$_SERVER['PHP_SELF'].'" >继续</a>'; exit; } 2. 上面调用的uc_user_login和uc_user_synlogin都是uc_client提供的函数: client.php function uc_user_login($username, $password, $isuid = 0, $checkques = 0, $questionid = '', $answer = '') { $isuid = intval($isuid); $return = call_user_func(UC_API_FUNC, 'user', 'login', array('username'=>$username, 'password'=>$password, 'isuid'=>$isuid, 'checkques'=>$checkques, 'questionid'=>$questionid, 'answer'=>$answer)); return UC_CONNECT == 'mysql' ? $return : uc_unserialize($return); } 当我们设置 uc_client成和uc_server通过post联系时,UC_API_FUNC的值是uc_api_post。这个函数调用了uc_fopen,在其中用PHP提供的fsockopen函数向发送了post请求: function uc_api_post($module, $action, $arg = array()) { .... $postdata = uc_api_requestdata($module, $action, $s); return uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20); } function uc_api_requestdata($module, $action, $arg='', $extra='') { $input = uc_api_input($arg); $post = "m=$module&a=$action&inajax=2&release=".UC_CLIENT_RELEASE."&input=$input&appid=".UC_APPID.$extra; return $post; } function uc_api_input($data) { $s = urlencode(uc_authcode($data.'&agent='.md5($_SERVER['HTTP_USER_AGENT'])."&time=".time(), 'ENCODE', UC_KEY)); return $s; } function uc_fopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE) { ... $out = "POST $path HTTP/1.0\r\n"; $out .= "Accept: */*\r\n"; //$out .= "Referer: $boardurl\r\n"; $out .= "Accept-Language: zh-cn\r\n"; $out .= "Content-Type: application/x-www-form-urlencoded\r\n"; $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n"; $out .= "Host: $host\r\n"; $out .= 'Content-Length: '.strlen($post)."\r\n"; $out .= "Connection: Close\r\n"; $out .= "Cache-Control: no-cache\r\n"; $out .= "Cookie: $cookie\r\n\r\n"; $out .= $post; ... if(function_exists('fsockopen')) { $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout); } elseif (function_exists('pfsockopen')) { $fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout); } else { $fp = false; } 举例而言,当用户填写了用户名为test1和密码为123451后,应用要将下面的数据提交给UC Server验证, username=test1&password=123451&isuid=0&checkques=0&questionid=&answer= 但其实发送过去的可能是下面的数据,原来的数据以加密的形式放在了input内: m=user&a=login&inajax=2&release=20110501&input=15c4UYDcKE%2B4yS8EWSDEunpH%2FkFphKAjhIjjw2MzOJ7jqQ1mmE1Ju7yqqxqtlrAUG6D1%2FDDF7v6vjMo6zHxvhWUZ6nI09dSqfrkm0UqZiXpkvFOL4HUHoFm0XtuMnHI6%2FlvN4NN8d0DxNaYyGCCfPZF9HRFADqXCrskDRhoy5RHmFIhPFZCRuOllVLtJGfslNztQ4cWRFNh%2B9w&appid=3 3. UC Server接到post请求后的处理: 文件control/user.php里的函数onlogin: if($isuid == 1) { $user = $_ENV['user']->get_user_by_uid($username); } elseif($isuid == 2) { $user = $_ENV['user']->get_user_by_email($username); } else { $user = $_ENV['user']->get_user_by_username($username); } ... $status = $user['uid']; ... return array($status, $user['username'], $password, $user['email'], $merge); 文件control/user.php里的函数onsynlogin: foreach($this->cache['apps'] as $appid => $app) { if($app['synlogin']) { $synstr .= '<script type="text/javascript" src="'.$app['url'].'/api/'.$app['apifilename'].'?time='.$this->time.'&code='.urlencode($this->authcode('action=synlogin&username='.$this->user['username'].'&uid='.$this->user['uid'].'&password='.$this->user['password']."&time=".$this->time, 'ENCODE', $app['authkey'])).'" reload="1"></script>'; if(is_array($app['extra']['extraurl'])) foreach($app['extra']['extraurl'] as $extraurl) { $synstr .= '<script type="text/javascript" src="'.$extraurl.'/api/'.$app['apifilename'].'?time='.$this->time.'&code='.urlencode($this->authcode('action=synlogin&username='.$this->user['username'].'&uid='.$this->user['uid'].'&password='.$this->user['password']."&time=".$this->time, 'ENCODE', $app['authkey'])).'" reload="1"></script>'; ... return $synstr; 继续上面的例子,UC Server发过去的数据也是加过密的(在code中):

4. 应用对事件的处理:api/uc.php: $code = @$_GET['code']; parse_str(_authcode($code, 'DECODE', UC_KEY), $get); ... $uc_note = new uc_note(); exit($uc_note->$get['action']($get, $post)); function synlogin($get, $post) { $uid = $get['uid']; $username = $get['username']; if(!API_SYNLOGIN) return API_RETURN_FORBIDDEN; header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'); _setcookie('Example_auth', _authcode($uid."\t".$username, 'ENCODE')); }
注:本文中的代码里的<符号如果后面的字符是a的话,在它们中间加了一个不应该有的空格,以避免Discuz在保存日志时自动改变日志内容。

发表评论 评论 (56 个评论)

回复 carry0987 2017-1-17 12:30 AM
天香公主: 我看了下你的文件 config/config_ucenter.php 里有这么一句:
    define('UC_API', 'http://www.fucknehs.com/uc_server');
其中的 http 是不是得改成 https ? ...
成功啦!!謝謝天香!
回复 carry0987 2017-1-17 12:06 AM
天香公主: 我看了下你的文件 config/config_ucenter.php 里有这么一句:
    define('UC_API', 'http://www.fucknehs.com/uc_server');
其中的 http 是不是得改成 https ? ...
我試試
回复 天香公主 2017-1-16 10:50 PM
carry0987:
貌似是頭像的問題...
我看了下你的文件 config/config_ucenter.php 里有这么一句:
    define('UC_API', 'http://www.fucknehs.com/uc_server');
其中的 http 是不是得改成 https ?
回复 carry0987 2017-1-16 10:09 PM
天香公主: 看不见图
而且頭像插件也因為https 失效了...
回复 carry0987 2017-1-16 10:07 PM
天香公主: 看不见图

貌似是頭像的問題...
回复 天香公主 2017-1-16 09:49 AM
carry0987: 天香求助!!
我要加入ssl,卻遇到這個問題...
看不见图
回复 carry0987 2017-1-16 09:15 AM
天香公主: 你认为我的修改"不完全"也许是由于你我考虑的对象不尽相同的缘故吧,我文中考虑的是一个单独安装的UCenter系统加上该安装软件带来的一个example应用,而你考虑 ...
天香求助!!
我要加入ssl,卻遇到這個問題...
回复 天香公主 2016-12-12 11:15 PM
ladyff: 这样改是不完全的
你这样修改后,你试一下修改头像,应该会有问题。
对于UC的通讯是不是正常,你需要到UC里看一下应用状态,看是不是通讯失败。而不是通过用户注 ...
你认为我的修改"不完全"也许是由于你我考虑的对象不尽相同的缘故吧,我文中考虑的是一个单独安装的UCenter系统加上该安装软件带来的一个example应用,而你考虑的是Discuz系统。如果Discuz还有其它地方也使用了fsockopen,那我同意那些地方有可能也需要做类似的修改。
回复 carry0987 2016-12-11 06:32 AM
天香公主: 发现以前有过的一句设置文件权限的代码被我无意中删了(avatar.inc.php 中的 @chmod($tmpfname, 0644); )。我在你那试过了,把这句加上去问题就解决了。下载文 ...
沒問題了!謝謝天香
回复 天香公主 2016-12-11 01:26 AM
carry0987: 喔喔...好的
我再去確認一下,因為還是不行..
发现以前有过的一句设置文件权限的代码被我无意中删了(avatar.inc.php 中的 @chmod($tmpfname, 0644); )。我在你那试过了,把这句加上去问题就解决了。下载文件也更新过了
http://www.bian-wang.com/discuz/data/userupload/10005/txgz_avatar2.zip
回复 carry0987 2016-12-4 10:19 PM
天香公主: 把文件 avatar.inc.php 里的下面这句
   $data = dfsockopen($imageurl);
改成了
   ini_set('user_agent', $_SERVER['HTTP_USER_AGENT']);
   $data = file_get ...
喔喔...好的
我再去確認一下,因為還是不行..
回复 天香公主 2016-12-3 11:57 AM
carry0987: 啊....可以告訴我改動的地方嗎?
把文件 avatar.inc.php 里的下面这句
   $data = dfsockopen($imageurl);
改成了
   ini_set('user_agent', $_SERVER['HTTP_USER_AGENT']);
   $data = file_get_contents($imageurl);

原因我过去解释过,见 http://www.bian-wang.com/discuz/home.php?mod=space&uid=10005&do=blog&id=1533&cid=8011
回复 carry0987 2016-12-3 07:34 AM
天香公主: 我刚更新了插件 http://www.bian-wang.com/discuz/data/userupload/10005/txgz_avatar2.zip,你再试试。

其实你遇到的问题与你用远程空间无关。而与你服务器的 ...
啊....可以告訴我改動的地方嗎?
回复 天香公主 2016-12-3 12:35 AM
carry0987: 我想可能是遠程空間的問題,我的遠程空間是https的,有SSL,可能是因為不支援SSL的文件吧,空間裏的讀取權限也沒問題,是777 ...
我刚更新了插件 http://www.bian-wang.com/discuz/data/userupload/10005/txgz_avatar2.zip,你再试试。

其实你遇到的问题与你用远程空间无关。而与你服务器的设置有关,我以前为此改过代码,但不知为何只改了一个文件而没改另一个,也许那时只在你那测试了上传图片一个选项。
回复 天香公主 2016-12-2 06:49 AM
carry0987: 還有,之前那個網友提到的https的問題,主要是什麼問題?出在哪個環節?
是说ladyff之前的评论?他是说UCenter中的ucserver部分作为一个网站添加了ssl后会出现问题。我发现一个原因是应用端里负责和服务端联系的函数没有考虑用https通讯的情形。昨天他说还有其它问题,要等我周末确认了再复。他提到了头像,但我试了没问题。
回复 carry0987 2016-12-2 04:38 AM
天香公主: 你说的对。我前面试的时候用了张以前放在相册里的图片,那种是存在你服务器本地的。现在新上传了一张图片到相册,再换头像就遇到了你说的问题。

插件好象没考虑 ...
還有,之前那個網友提到的https的問題,主要是什麼問題?出在哪個環節?
回复 天香公主 2016-12-2 12:25 AM
carry0987: 我想可能是遠程空間的問題,我的遠程空間是https的,有SSL,可能是因為不支援SSL的文件吧,空間裏的讀取權限也沒問題,是777 ...
你说的对。我前面试的时候用了张以前放在相册里的图片,那种是存在你服务器本地的。现在新上传了一张图片到相册,再换头像就遇到了你说的问题。

插件好象没考虑到相册图片在远程空间的情况,所以用了错误的图片路径。等我周末改好了再告诉你。
回复 carry0987 2016-12-2 12:10 AM
天香公主: 是指我先前做的那个用canvas的插件还是后来做的那个不用canvas的?

我刚试了下你的网站没遇到问题。也许你遇到的问题和图片有关?你换个小点的图片试试。如果小 ...
我想可能是遠程空間的問題,我的遠程空間是https的,有SSL,可能是因為不支援SSL的文件吧,空間裏的讀取權限也沒問題,是777
回复 天香公主 2016-12-1 11:50 PM
carry0987: 天香,頭像上傳插件(html5)選擇相冊裏的圖片時,出現上傳失敗
是指我先前做的那个用canvas的插件还是后来做的那个不用canvas的?

我刚试了下你的网站没遇到问题。也许你遇到的问题和图片有关?你换个小点的图片试试。如果小图片没问题的话,把你那个有问题的图片的url给我,我来试试
回复 carry0987 2016-12-1 11:21 PM
天香公主: 我试了下我的例子,在UCenter网站上使用了SSL后,的确它的应用和它连不上了。发现问题出在这些应用使用的client.php文件里的函数uc_fopen没有考虑UCenter用SSL的 ...
天香,頭像上傳插件(html5)選擇相冊裏的圖片時,出現上傳失敗

facelist doodle 涂鸦板

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

小黑屋|Archiver|彼岸网  

Powered by Discuz! X3.1 © 2001-2014 Comsenz Inc.
GMT-4, 2024-3-29 02:36 AM , Processed in 0.027111 second(s), 10 queries. ,ApcOn

返回顶部