核心原理
双向同步登录的核心思想是:让两个独立的用户系统共享同一个用户身份验证(登录/登出)的“状态”。

由于 DedeCMS 和 Discuz! 是两个独立的程序,它们各自有自己的用户数据库表和 Session 管理机制,直接让它们共享 Session 是不现实的,因此最常用也是最可靠的方法是通过 Cookie 和数据库联动来实现。
基本流程如下:
-
用户在 A 系统登录:
- A 系统验证用户名和密码。
- A 系统在自己的数据库中更新该用户的登录状态(将
dede_member表中的logintime更新为当前时间)。 - A 系统向用户的浏览器写入一个特殊的、加密的 Cookie(
dede_auth)。
-
A 系统触发 B 系统登录:
(图片来源网络,侵删)- A 系统在成功写入自己的 Cookie 后,会立即通过 HTTP 请求(通常是
file_get_contents或curl)去访问 B 系统的一个“同步登录接口”页面。 - 这个请求会携带用户的唯一标识(如
uid)。 - B 系统的“同步登录接口”接收到请求后,验证请求的合法性(检查来源域名、验证
uid是否有效)。 - 验证通过后,B 系统模拟该用户登录,更新自己数据库中的用户登录状态,并向浏览器写入 B 系统自己的登录 Cookie(
Discuz_auth)。
- A 系统在成功写入自己的 Cookie 后,会立即通过 HTTP 请求(通常是
-
反向流程(B -> A):
当用户在 B 系统登录或登出时,B 系统会以同样的方式去触发 A 系统的同步登录/登出接口。
图示说明:
[用户] --登录--> [DedeCMS]
1. DedeCMS 验证用户,设置自己的 Cookie
2. DedeCMS 调用 Discuz! 的同步登录接口
3. Discuz! 接口接收请求,模拟登录,设置自己的 Cookie
4. 用户被重定向到目标页面,此时两个系统都已登录
[用户] --退出--> [Discuz!]
1. Discuz! 清除自己的 Cookie
2. Discuz! 调用 DedeCMS 的同步登出接口
3. DedeCMS 接口接收请求,清除登录状态
4. 用户被重定向,两个系统都已登出
实现步骤(以 DedeCMS V5.7 + Discuz! X3.4 为例)
在开始之前,请务必备份你的数据库和网站文件!
第 1 步:统一用户表(可选但推荐)
为了数据一致性,最理想的情况是让两个系统共享同一个用户表,但这通常需要复杂的二次开发,且两个系统的用户字段结构不同,实现难度较大。
对于大多数用户来说,维持两个独立的用户表,但通过程序同步数据是更常见的选择,我们这里就按照这个思路来讲解。
第 2 步:在 Discuz! 中创建同步登录 API 文件
这个文件将作为 DedeCMS 调用 Discuz! 的入口。
- 在 Discuz! 的根目录下(
bbs/),创建一个新文件,命名为uc.php。 - 将以下代码复制到
uc.php文件中:
<?php
/**
* Discuz! 同步登录接口
* 由 DedeCMS 调用,实现 Discuz! 侧的登录
*/
// 定义 Discuz! 根目录,请确保路径正确
define('CURSCRIPT', 'uc');
define('IN_DISCUZ', true);
define('UC_CLIENT_VERSION', '1.0.0'); // 版本号,可自定义
define('UC_CLIENT_RELEASE', '20121112'); // 发布日期,可自定义
// 引入 Discuz! 的核心配置文件
require_once './config/config_global.php'; // 注意路径,根据你的目录结构调整
require_once './source/class/class_core.php';
// 初始化 Discuz! 数据库对象
$discuz = C::app();
// 关闭 Discuz! 的错误提示,防止 API 调用时输出 HTML
$discuz->init_cron = false;
$discuz->init_session = false;
$discuz->init();
// 引入同步登录相关的函数库
require_once './source/function/function_core.php';
require_once './source/function/function_member.php';
// --- 核心逻辑 ---
// 检查请求方式,只允许 POST 请求
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
exit('Request Method Error');
}
// 获取 DedeCMS 传递过来的用户ID
$uid = intval($_POST['uid']);
if (empty($uid)) {
exit('UID is empty');
}
// --- 安全性校验(非常重要!) ---
// 你可以在这里增加一个密钥验证,防止被恶意调用
// DedeCMS 调用时传递一个 $key = md5('your_secret_key' . $uid);
// 然后在这里验证 $key 是否与 $_POST['key'] 相等
// if ($_POST['key'] !== md5('your_secret_key' . $uid)) {
// exit('Authentication failed');
// }
// -------------------------
// 调用 Discuz! 内部函数,模拟用户登录
// synlogin($uid) 会自动设置 Discuz! 的 Cookie 并输出 JavaScript 代码
// 我们需要捕获这个输出,然后执行它
ob_start();
synlogin($uid);
$script = ob_get_contents();
ob_end_clean();
// 输出 JavaScript 代码,由 DedeCMS 页面执行
echo $script;
?>
代码解释:
- 这个文件首先加载了 Discuz! 的核心环境和配置。
- 它接收一个 POST 请求,参数是
uid(DedeCMS 中的用户ID)。 - 安全性:上面的代码中留了一个安全校验的注释框,强烈建议你实现这个密钥校验,否则任何知道这个接口的人都能模拟登录你论坛的任意用户。
synlogin($uid)是 Discuz! 提供的内部函数,它会生成一段 JavaScript 代码,这段代码的作用就是设置浏览器的 Discuz! 登录 Cookie。- 我们用
ob_start()和ob_get_contents()捕获这段 JS 代码,echo出来。
第 3 步:修改 DedeCMS 的登录处理文件
我们需要让 DedeCMS 在用户成功登录后,去调用刚刚创建的 uc.php。
- 找到 DedeCMS 的会员登录处理文件,通常位于
/member/login.php。 - 在该文件中,找到登录成功后的代码段,在
if($rs)里面,在ShowMsg()函数之前,添加如下代码:
// 在 DedeCMS /member/login.php 中添加
// ... 原有代码 ...
if($rs) {
// 原有的登录成功逻辑
$dsql->ExecuteNoneQuery("UPDATE `dede_member` SET `logintime`='".time()."', `loginip`='".GetIP()."' WHERE `mid`='".$rs['mid']."';");
$safecv = md5($rs['pwd'].$rs['secques']);
$loginsum = md5($rs['user'].$rs['pwd'].$cfg_cookie_encode);
// 设置 DedeCMS 自身的 Cookie
SetCookie("DedeUserID", $rs['mid'], time()+3600*24*30, '/');
SetCookie("DedeLoginTime", time(), time()+3600*24*30, '/');
SetCookie("DedeLoginTime", time(), time()+3600*24*30, '/');
SetCookie("dede_logintime", time(), time()+3600*24*30, '/');
SetCookie("dede_loginuser", $rs['user'], time()+3600*24*30, '/');
SetCookie("dede_loginpwd", $safecv, time()+3600*24*30, '/');
// --- 新增:同步登录到 Discuz! ---
$bbs_url = 'http://你的域名.com/bbs/'; // <-- 修改为你的 Discuz! 论坛地址
$sync_api_url = $bbs_url . 'uc.php';
$uid = $rs['mid']; // DedeCMS 的用户ID
// 使用 file_get_contents 或 curl 调用 Discuz! 的同步登录接口
$post_data = array(
'uid' => $uid,
// 'key' => md5('your_secret_key' . $uid) // 如果你设置了密钥校验,请加上这一行
);
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => http_build_query($post_data),
'timeout' => 10 // 设置超时,避免用户等待
)
));
// 调用接口,并捕获返回的 JS 代码
$javascript_code = @file_get_contents($sync_api_url, false, $context);
// 将返回的 JS 代码输出到页面,由浏览器执行
// 这通常在 ShowMsg 之前,或者在页面的 footer 中执行
// 为了简单,我们可以直接 echo,但更好的方式是存入一个变量,在模板中输出
echo $javascript_code;
// --- 新增结束 ---
// 原有的登录成功提示
ShowMsg('成功登录,正在进入...',$gourl,0,2000);
exit();
}
代码解释:
- 我们获取了当前登录用户的
mid(DedeCMS 的用户ID)。 - 构造了调用 Discuz!
uc.php的 URL 和 POST 数据。 - 使用
file_get_contents发送了一个 POST 请求到 Discuz! 的接口。 - Discuz! 的接口会返回一段 JS 代码,我们直接
echo它,当 DedeCMS 的登录页面加载时,这段 JS 也会被执行,从而在浏览器中设置 Discuz! 的登录 Cookie。
第 4 步:实现反向同步(Discuz! -> DedeCMS)
这个过程和上面是镜像的。
-
在 DedeCMS 中创建同步登录接口:
- 在 DedeCMS 根目录()创建一个文件,
api/dede_sync.php。 - 写入一个 PHP 脚本,该脚本接收
uid,验证后,模拟 DedeCMS 的登录(通常也是输出一段 JS 代码来设置 DedeCMS 的 Cookie)。
- 在 DedeCMS 根目录()创建一个文件,
-
修改 Discuz! 的登录处理文件:
- 找到 Discuz! 的登录处理文件
/source/class/class_member.php中的login()方法,或者更简单的方式是修改模板/template/default/login/login.htm在登录成功后通过 JavaScript 发起一个请求。 - 在 Discuz! 登录成功后,调用 DedeCMS 的
dede_sync.php接口,传递 Discuz! 的uid(注意:这里需要建立一个 DedeCMS的uid和Discuz!的uid的映射关系,最简单的是让它们相同,或者通过一个中间表关联)。
- 找到 Discuz! 的登录处理文件
注意:由于 Discuz! 的登录流程更复杂(涉及 AJAX 请求等),直接修改其核心文件风险较高,一个更稳妥的方法是修改 Discuz! 的登录模板,在登录成功的 JavaScript 回调中,加入一个 AJAX 请求去调用 DedeCMS 的接口。
同步登出
同步登出的原理和登录完全一样,只是流程相反,并且通常是清除 Cookie。
-
在 Discuz! 中创建同步登出接口 (
uc.php可以扩展,或新建uc_logout.php):- 这个接口调用 Discuz! 的
synlogout()函数,该函数会生成一段清除 Discuz! 登录 Cookie 的 JS 代码。
- 这个接口调用 Discuz! 的
-
在 DedeCMS 中创建同步登出接口 (
dede_sync.php或新建dede_logout.php):这个接口生成一段清除 DedeCMS 登录 Cookie 的 JS 代码。
-
互相调用:
- 当用户在 DedeCMS 登出时,调用 Discuz! 的同步登出接口。
- 当用户在 Discuz! 登出时,调用 DedeCMS 的同步登出接口。
重要注意事项与常见问题
-
安全性是重中之重:
- 必须为你的同步 API 接口设置密钥验证,防止被恶意调用,任何知道你接口地址的人,都可以伪造请求登录你的网站。
- 可以使用
$secret_key = 'your_long_random_string';,$key = md5($secret_key . $uid)的方式传递和验证。
-
用户 ID 映射问题:
- 这是最容易出错的地方,DedeCMS 的用户 ID 在
dede_member表的mid字段,Discuz! 的用户 ID 在pre_common_member表的uid字段。 - 最佳实践:在用户注册时,让两个系统生成相同的用户 ID,这需要修改 DedeCMS 和 Discuz! 的注册流程,让它们共享一个 ID 生成器(使用数据库的自增 ID,但跨库操作很麻烦)。
- 次优实践:建立一个映射表
dede_bbs_map,包含dede_uid和bbs_uid字段,在用户注册时,同时向两个系统注册,并记录下这个映射关系,同步登录时,通过这个映射表找到对方的用户 ID。
- 这是最容易出错的地方,DedeCMS 的用户 ID 在
-
Cookie 作用域和路径:
- 确保 Cookie 的作用域(
domain)和路径(path)设置正确,使得a.com和bbs.a.com能够互相读写 Cookie,通常设置为.a.com即可。
- 确保 Cookie 的作用域(
-
错误处理和超时:
同步请求是异步的,如果其中一个系统挂了,不应该影响另一个系统的登录,在调用 API 时,设置一个合理的超时时间(如 1-2 秒),如果超时,就忽略错误,只保证当前系统的登录成功。
-
版本兼容性:
不同版本的 DedeCMS 和 Discuz!,其文件路径、函数名、数据库结构可能不同,以上代码是基于较老版本的,如果你使用的是最新版本(如 DedeCMS 5.7sp2, Discuz! X3.5),可能需要根据实际情况调整文件路径和函数调用。
-
性能影响:
每次登录都会发起一次跨域 HTTP 请求,会增加服务器负担和用户登录的延迟,确保你的服务器性能足够,API 接口处理高效。
更高级的方案:单点登录
双向同步登录是一种“伪”单点登录,因为用户最终还是需要在一处登录,然后系统去同步另一处。
真正的单点登录流程如下:
- 用户访问 A 系统,发现未登录。
- A 系统重定向到统一的认证中心。
- 用户在认证中心登录。
- 认证中心验证成功后,分别向 A 系统和 B 系统发放“票据”(Ticket)。
- A 系统和 B 系统收到票据后,向认证中心验证票据的有效性。
- 验证通过后,用户就在 A 和 B 系统中同时处于登录状态。
SSO 实现起来更复杂,通常需要引入一个专门的认证服务,但对于 DedeCMS + Discuz! 这种架构,自己实现 SSO 难度很大,双向同步登录仍然是目前最主流、最实用的解决方案。
希望这份详细的指南能帮助你成功实现 DedeCMS 和 Discuz! 的双向同步登录!
