Discuz粉丝网(www.discuzfans.net)

 找回密码
 立即注册

扫一扫,微信登录

搜索
热搜: 活动 交友 discuz

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

[复制链接]
 管理员 admin 发表于 2016-12-6 00:18:50 |阅读模式
据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服务端里注册,

关键的一点是两者要用一个相同的通讯密钥。当一方和对方联系时,它要将通话内容用这个密钥加密,对方接到加密的内容后再用该密钥解密。这样做的好处是每方都能确认和它通讯的的确是对方。


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

这个流程的关键点有:
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
  1. echo '<form method="post" action="'.$_SERVER['PHP_SELF'].'?example=login">';
  2. echo '登录:';
  3. echo '<dl><dt>用户名</dt><dd><input name="username"></dd>';
  4. echo '<dt>密码</dt><dd><input name="password" type="password"></dd></dl>';
  5. echo '<input name="submit" type="submit"> ';
  6. echo '</form>';
复制代码
和对用户提交登陆资料的处理: examples/code/login_db.php
  1. //通过接口判断登录帐号的正确性,返回值为数组
  2. list($uid, $username, $password, $email) = uc_user_login($_POST['username'], $_POST['password']);

  3. setcookie('Example_auth', '', -86400);
  4. if($uid > 0) {
  5.         if(!$db->result_first("SELECT count(*) FROM {$tablepre}members WHERE uid='$uid'")) {
  6.                 //判断用户是否存在于用户表,不存在则跳转到激活页面
  7.                 $auth = rawurlencode(uc_authcode("$username\t".time(), 'ENCODE'));
  8.                 echo '您需要需要激活该帐号,才能进入本应用程序<br>< a href="'.$_SERVER['PHP_SELF'].'?example=register&action=activation&auth='.$auth.'">继续</a>';
  9.                 exit;
  10.         }
  11.         //用户登陆成功,设置 Cookie,加密直接用 uc_authcode 函数,用户使用自己的函数
  12.         setcookie('Example_auth', uc_authcode($uid."\t".$username, 'ENCODE'));
  13.         //生成同步登录的代码
  14.         $ucsynlogin = uc_user_synlogin($uid);
  15.         echo '登录成功'.$ucsynlogin.'<br>< a href="'.$_SERVER['PHP_SELF'].'" >继续</a>';
  16.         exit;
  17. }
复制代码
2. 上面调用的uc_user_login和uc_user_synlogin都是uc_client提供的函数: client.php
  1. function uc_user_login($username, $password, $isuid = 0, $checkques = 0, $questionid = '', $answer = '') {
  2.         $isuid = intval($isuid);
  3.         $return = call_user_func(UC_API_FUNC, 'user', 'login', array('username'=>$username, 'password'=>$password, 'isuid'=>$isuid, 'checkques'=>$checkques, 'questionid'=>$questionid, 'answer'=>$answer));
  4.         return UC_CONNECT == 'mysql' ? $return : uc_unserialize($return);
  5. }
复制代码
当我们设置 uc_client成和uc_server通过post联系时,UC_API_FUNC的值是uc_api_post。这个函数调用了uc_fopen,在其中用PHP提供的fsockopen函数向发送了post请求:
  1. function uc_api_post($module, $action, $arg = array()) {
  2.         ....
  3.         $postdata = uc_api_requestdata($module, $action, $s);
  4.         return uc_fopen2(UC_API.'/index.php', 500000, $postdata, '', TRUE, UC_IP, 20);
  5. }
  6. function uc_api_requestdata($module, $action, $arg='', $extra='') {
  7.         $input = uc_api_input($arg);
  8.         $post = "m=$module&a=$action&inajax=2&release=".UC_CLIENT_RELEASE."&input=$input&appid=".UC_APPID.$extra;
  9.         return $post;
  10. }
  11. function uc_api_input($data) {
  12.         $s = urlencode(uc_authcode($data.'&agent='.md5($_SERVER['HTTP_USER_AGENT'])."&time=".time(), 'ENCODE', UC_KEY));
  13.         return $s;
  14. }
  15. function uc_fopen($url, $limit = 0, $post = '', $cookie = '', $bysocket = FALSE, $ip = '', $timeout = 15, $block = TRUE) {
  16.         ...
  17.                 $out = "POST $path HTTP/1.0\r\n";
  18.                 $out .= "Accept: */*\r\n";
  19.                 //$out .= "Referer: $boardurl\r\n";
  20.                 $out .= "Accept-Language: zh-cn\r\n";
  21.                 $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
  22.                 $out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
  23.                 $out .= "Host: $host\r\n";
  24.                 $out .= 'Content-Length: '.strlen($post)."\r\n";
  25.                 $out .= "Connection: Close\r\n";
  26.                 $out .= "Cache-Control: no-cache\r\n";
  27.                 $out .= "Cookie: $cookie\r\n\r\n";
  28.                 $out .= $post;
  29.         ...

  30.         if(function_exists('fsockopen')) {
  31.                 $fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
  32.         } elseif (function_exists('pfsockopen')) {
  33.                 $fp = @pfsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
  34.         } else {
  35.                 $fp = false;
  36.         }
复制代码
举例而言,当用户填写了用户名为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:
  1. if($isuid == 1) {
  2.     $user = $_ENV['user']->get_user_by_uid($username);
  3. } elseif($isuid == 2) {
  4.     $user = $_ENV['user']->get_user_by_email($username);
  5. } else {
  6.     $user = $_ENV['user']->get_user_by_username($username);
  7. }
  8. ...
  9. $status = $user['uid'];
  10. ...
  11. return array($status, $user['username'], $password, $user['email'], $merge);
复制代码
文件control/user.php里的函数onsynlogin:
  1. foreach($this->cache['apps'] as $appid => $app) {
  2.     if($app['synlogin']) {
  3.         $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>';
  4.         if(is_array($app['extra']['extraurl'])) foreach($app['extra']['extraurl'] as $extraurl) {
  5.             $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>';
  6. ...
  7. return $synstr;
复制代码
继续上面的例子,UC Server发过去的数据也是加过密的(在code中):

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


原文:http://www.bian-wang.com/discuz/home.php?mod=space&uid=10005&do=blog&id=1536
输入图片说明

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册 扫一扫,微信登录

x
回复

使用道具 举报

 中级会员 执念@ 发表于 2016-12-6 20:04:51
我的uc一直显示通信不成功.......但是改头像、注册、改密码都没问题
回复

使用道具 举报

 中级会员 suoujc 发表于 2018-1-20 13:33:45
哇,这么多啊,这要消化多久啊
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 扫一扫,微信登录

本版积分规则

小黑屋|手机版|Discuz粉丝网 ( 浙ICP备10214163号 )star

GMT+8, 2019-8-22 17:41 , Processed in 0.093079 second(s), 14 queries .

Powered by Discuz! Lite

© 2001-2019 Comsenz Inc.

快速回复 返回顶部 返回列表