很早以前就做过dz的同步登陆,这几天又和thinkphp结合搞了一下,做下记录。
这个教程基本不止适合thinkphp哦~。其它程序想实现同步登陆、退出、消息、注册都可以参照此教程。
首先,去下载我写好的代码压缩包,CSDN下载链接。(没办法,我的博客暂时没开发文件上传,还考虑到下载速度的问题)
1、移动文件
解压压缩包后
将 api 文件夹复制到 thinkphp 根目录(application同级目录)
将 Ucenter 文件夹复制到 application 目录
2、修改配置文件
修改 Application/Ucenter/Conf/config.php ,内容为dz后台添加应用的内容,勾选同步登陆
内容为
define('UC_CONNECT', 'mysql'); define('UC_DBHOST', '127.0.0.1'); define('UC_DBUSER', 'root'); define('UC_DBPW', '123456'); define('UC_DBNAME', 'bbs'); define('UC_DBCHARSET', 'utf8'); define('UC_DBTABLEPRE', '`bbs`.pre_ucenter_'); define('UC_DBCONNECT', '0'); define('UC_KEY', 'c49dz25t6uovDHGDnu50xVoBTqPVMZf42RqczBs'); define('UC_API', 'http://bbs.test.com/uc_server'); define('UC_CHARSET', 'utf-8'); define('UC_IP', ''); define('UC_APPID', '2'); define('UC_PPP', '20');
修改 Application/Ucenter/config.inc.php ,按照注释填写即可,通信相关中的内容可参照上面 config.php 中的配置
内容为
define('UC_CONNECT', 'mysql'); // 连接 UCenter 的方式: mysql/NULL, 默认为空时为 fscoketopen() // mysql 是直接连接的数据库, 为了效率, 建议采用 mysql //数据库相关 (mysql 连接时, 并且没有设置 UC_DBLINK 时, 需要配置以下变量) define('UC_DBHOST', '127.0.0.1'); // UCenter 数据库主机 define('UC_DBUSER', 'root'); // UCenter 数据库用户名 define('UC_DBPW', '123456'); // UCenter 数据库密码 define('UC_DBNAME', 'bbs'); // UCenter 数据库名称 define('UC_DBCHARSET', 'utf8'); // UCenter 数据库字符集 define('UC_DBTABLEPRE', '`bbs`.pre_ucenter_'); // UCenter 数据库表前缀 //通信相关 define('UC_KEY', 'c49dz25t6uovDHGDnu50xVoBTqPVMZf42RqczBs'); // 与 UCenter 的通信密钥, 要与 UCenter 保持一致 define('UC_API', 'http://bbs.test.com/uc_server'); // UCenter 的 URL 地址, 在调用头像时依赖此常量 define('UC_CHARSET', 'utf-8'); // UCenter 的字符集 define('UC_IP', ''); // UCenter 的 IP, 当 UC_CONNECT 为非 mysql 方式时, 并且当前应用服务器解析域名有问题时, 请设置此值 define('UC_APPID', '2'); // 当前应用的 ID define('UC_PPP', '20'); $dbhost = '127.0.0.1'; // 数据库服务器 $dbuser = 'root'; // 数据库用户名 $dbpw = '123456'; // 数据库密码 $dbname = 'bbs'; // 数据库名 $pconnect = 0; // 数据库持久连接 0=关闭, 1=打开 $tablepre = '`bbs`.pre_ucenter_'; // 表名前缀, 同一数据库安装多个论坛请修改此处 $dbcharset = 'utf8'; // MySQL 字符集, 可选 'gbk', 'big5', 'utf8', 'latin1', 留空为按照论坛字符集设定 //同步登录 Cookie 设置 $cookiedomain = ''; // cookie 作用域 $cookiepath = '/'; // cookie 作用路径
3、方法解释和使用
Application/Ucenter/Controller/IndexController.php 为同步登录的控制器,内容如下
namespace Ucenter\Controller; require_once APP_PATH . "Ucenter/config.inc.php"; require_once APP_PATH . "Ucenter/uc_client/client.php"; class IndexController extends \Think\Controller { public function tlogin($iusername, $ipassword) { //通过接口判断登录帐号的正确性,返回值为数组 list($uid, $username, $password, $email, $merge, $phone) = uc_user_login($iusername, $ipassword); setcookie('Example_auth', '', -86400); if ($uid > 0) { //用户登陆成功,设置 Cookie,加密直接用 uc_authcode 函数 setcookie('Example_auth', uc_authcode($uid . "\t" . $username, 'ENCODE')); //生成同步登录的代码 $ucsynlogin = uc_user_synlogin($uid); return $this->_return($ucsynlogin, '', true); } elseif ($uid == -1) { return $this->_return($uid, '用户不存在,或者被删除'); } elseif ($uid == -2) { return $this->_return($uid, '密码错误'); } else { return $this->_return($uid, '未定义的操作'); } } public function autologin() { $auth = $_COOKIE['Example_auth']; $auth = daddslashes(explode("\t", uc_authcode($auth, 'DECODE'))); list($discuz_uid, $discuz_uname) = empty($auth) || count($auth) < 2 ? array('', '') : $auth; if ($discuz_uid) { $user = uc_get_user($discuz_uid, 1); } if ($user) { session('user_id', $user[0]); session('user', $user); session('user.user_id', $user[0]); session('user.user_name', $user[1]); } } public function logout() { $ucsynlogout = uc_user_synlogout(); session('user', null); session('user_id', null); setcookie('Example_auth',null); return $ucsynlogout; } private function _return($data, $msg, $status = false) { return ['data' => $data, 'info' => $msg, 'status' => $status]; } }
这里要使用 php 自带的 setcookie 和 $_COOKIE 来设置和获取cookie,因为tp中的 cookie 方法设置有前缀,有可能获取不到
tlogin为登录,autologin为自动登录
测试
在 Application/Home/IndexController.php 中的方法,此 IndexController 继承一个叫 BaseController 的基类
public function test(){ $ucenter = new \Ucenter\Controller\IndexController(); $data = $ucenter->tlogin('admin', 'shiba123.!'); echo '<pre>'; print_r($data); echo '</pre>'; } public function test1(){ $ucenter = new \Ucenter\Controller\IndexController(); $ucenter->autologin(); echo '<pre>'; print_r($_SESSION['user']); echo '</pre>'; }
访问 http://localhost/?m=home&a=test
返回
Array ( [data] => '<script type="text/javascript" src="http://bbs.test.com/api/uc.php?time=1522640515&code=生成的登录key" reload="1"></script>' [info] => [status] => 1 )
status 为 1 表示成功,data会返回同步登录的代码,将此代码插入到任意一个dom中即可同步登录,此处是通知 bbs.test.com 这个论坛登录
此时就可以执行 autologin登录,当然这个自动登录放在 BaseController 中更好
访问 http://localhost/?m=home&a=test1
返回
Array ( [0] => 1 [1] => admin [2] => admin@admin.com [user_id] => 1 [user_name] => admin )
根据 $_SESSION['user_id'] 就可以知道是否登录
如何实现自动登录?
在 BaseController 中的 __construct 中加入如下到代码
$ucenter = new \Ucenter\Controller\IndexController(); $ucenter->autologin();
同步退出,执行 logout 将返回的内容插入到 dom中,和同步登录类似
$ucenter = new \Ucenter\Controller\IndexController(); $data = $ucenter->logout();
比如想实现注册(uc_user_register),发消息(uc_pm_send)等功能,使用uc_client中的对应方法即可,还是很简单的,我就没一一列出
4、下面来讲讲同步登录的原理,方便以后出问题了排查
场景:在discuz中添加了一个叫官网的应用,在官网中登录,discuz中实现同步登录
在官网中执行 uc_user_login 方法的时候,首先会检查 Application/Ucenter/uc_client/data/cache/apps.php 这个文件中是否有多个应用,若是一个,当然就不会执行同步登录
若是多个应用,将会 uc_api_post 远程请求 discuz 根目录的 uc_server/control/user.php 中的 onsynlogin 方法,将 uc_server/data/cache/apps.php 中的应用缓存数据获取到,并执行加密
function onsynlogin() { $this->init_input(); $uid = $this->input('uid'); if ($this->app['synlogin']) { if ($this->user = $_ENV['user']->get_user_by_uid($uid)) { $synstr = ''; foreach ($this->cache['apps'] as $appid => $app) { if ($app['synlogin']) { if ($app['appid'] != $this->app['appid']) { $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; } } return ''; }
code就是加密的内容,加密内容为时间戳,用户名,用户id,加密key为apps.php中authkey,必须和 Application/Ucenter/config.inc.php 中的 UC_KEY 相同,uc.php中才能解密
返回的 $synstr 是一段js代码,如下
<script type="text/javascript" src="http://bbs.test.com/api/uc.php?time=1522640515&code=加密code" reload="1"></script>
若是多个应用,将会有多个js,将此返回的内容插入到页面中,js会请求论坛根目录下的 api/uc.php
通过解密后执行 synlogin 方法,注意这一段代码
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
这是跨域执行cookie的关键,将会设置一个名叫 auth 的cookie,dz或判断这个 auth 来执行登录。
同理,dz登录,通知官网应用,也会执行 synlogin ,不过是设置的 Example_auth 这个cookie,所以,我们在官网应用中执行自动登录时,要获取 Example_auth 这个cookie,请看 Ucenter中的 autologin 方法
若是发现不能同步登录,请检查对应应用下的 uc_client/data/cache/apps.php 这个文件,还有 uc_server/data/cache/apps.php ,保证数量是和dz后台配置的应用数相同,若是不相同,删掉即可