CAS客户端整合(一) Discuz!
有好几个系统需要接入CAS,所以登录模块统统需要重构
版本
- CAS服务端是Java的 Cas-server-4.0
- CAS的php客户端 是 phpCAS-1.2.0
- 论坛版本是 Discuz!X3.3
Discuz! 登录流程
因为discuz原来的流程是验证自己的一套用户密码体系,现在我们需要将这个验证过程放在 CAS-server ,然后通过绑定的 php-cas-client
来获取登录状态。由这个登录状态来决定需不需要初始化用户。
原流程
简(jian)单(nan)研究 Discuz! 的源码,大致画出它的登录流程
CAS登录流程
我们修改后的登录流程,橙色为需要修改的部分
这里可以根据自己的业务场景进行调整,大致的流程是一样的。可能在是否本地cookie自动登录这点会有区别,在文末总结里会对这一点谈谈我个人的分析。
整合 Discuz! 与 phpCAS
画好了流程图,然后可以开始动手写代码了。
phpCAS接入
根目录下引入整个phpCAS客户端/cas/
,新建一个CasClient.php
用来做一些初始化工作。
/*
* path : /cas/CasClient.php
*/
define ( 'CAS_SERVER_HOST', 'localhost' );
define ( 'CAS_SERVER_PORT', 34382 );
define ( 'CAS_SERVER_PATH', "/cas-server" );
include_once (dirname ( __FILE__ ) . '/CAS.php');
// debug logfile name
phpCAS::setDebug ('./cas.log');
// initialize phpCAS
phpCAS::client ( CAS_VERSION_2_0, CAS_SERVER_HOST, CAS_SERVER_PORT, CAS_SERVER_PATH );
// no SSL validation for the CAS server
phpCAS::setNoCasServerValidation ();
// sync logout requests
phpCAS::handleLogoutRequests();
//var_dump(phpCAS::isAuthenticated());
然后在discuz中包含它,在 /source/class/class_core.php
添加一行:
error_reporting(E_ALL);
define('IN_DISCUZ', true);
define('DISCUZ_ROOT', substr(dirname(__FILE__), 0, -12));
define('DISCUZ_CORE_DEBUG', false);
define('DISCUZ_TABLE_EXTENDABLE', false);
// include phpCAS
require_once DISCUZ_ROOT."cas/CasClient.php";
set_exception_handler(array('core', 'handleException'));
**我引入的phpCAS版本是用 global 变量来存放 PHPCAS_CLIENT,Discuz在程序初始化的时候会清空全局变量(
discuz_application->_init_env()
),因此导致在后面无法获取到PHPCAS_CLIENT
变量,出现phpCAS error: phpCAS::isAuthenticated(): this method should not be called before phpCAS::client() or phpCAS::proxy()
错误。我的解决方案是把/cas/CAS.php
里的 global 变量改成了 static,问题解决。 **
phpCAS 客户端是通过 session 来记录
isAuthenticated()
状态,引入phpCAS的地方可以获取正确的session, 在后面的业务代码中就为null。聪明的你已经想到因为跟上面相同的原因,全局的 session 变量在 Discuz 初始化的时候被清空了。因此对/source/class/discuz/discuz_application.php
进行如下修改private function _init_env() {
...
foreach ($GLOBALS as $key => $value) {
// if session of phpCAS, keep it.
if (!isset($this->superglobal[$key])) {
if ($key == '_SESSION') {
$temp_phpCAS = $_SESSION['phpCAS'];
$GLOBALS[$key] = null; unset($GLOBALS[$key]);
$_SESSION['phpCAS'] = $temp_phpCAS;
$temp_phpCAS = null; unset($temp_phpCAS);
continue;
}
$GLOBALS[$key] = null; unset($GLOBALS[$key]);
}
}
...
这样我们就可以在discuz的其他地方正确获取到phpCAS的client对象。
Discuz 的登录过程
登录过程中有2个地方需要修改。第一是页面初始化的时候,检查cas是否已登录,如果已登录直接初始化用户登录信息;第二处是如果页面发起了登录请求,我们需要将请求引导到cas-server端登录,验证完成后返回。
页面初始化
修改位于/source/class/discuz/discuz_appliation.php
,大致是 455 行 _init_user()
方法:
if($this->init_user) {
/**
* login via cas
*/
if (phpCAS::isAuthenticated()) {
// 根据自己实际情况获取用户字段,这里论坛账号为中文名
// 因此用这个中文名到数据库中查找用户uid进行初始化
$username = phpCAS::getAttribute('cname');
$db_user_info = DB::fetch_first("SELECT `uid`,`username`,`password`,`email` FROM ". DB::table('ucenter_members') ." WHERE `username`='$username' ");
$discuz_pw = $db_user_info['password'];
$discuz_uid = $db_user_info['uid'];
// 下面这部分跟原来的验证过程一致
if ($db_user_info) {
$user = getuserbyuid($discuz_uid, 1);
if(isset($user['_inarchive'])) {
C::t('common_member_archive')->move_to_master($discuz_uid);
}
$this->var['member'] = $user;
} else {
$user = array();
$this->_init_guest();
}
} else {
// original discuz auth via cookie
以上修改的意思是,页面初始化的时候会检查cas-server的用户登录状态,如果已有用户登录,获取用户名,在discuz初始化这个用户的登录状态。
登录跳转
接下来要处理的问题是,当用户没有登录的时候,如何从discuz登录到 cas-server。
discuz的所有登录(管理后台除外)都会由/source/class/class_member.php
的on_login()
方法进行处理。
/*
* discuz 原先的逻辑
if(!submitcheck('loginsubmit', 1, $seccodestatus)) {
登录界面
}
else {
验证账号密码
}
*/
if ( TRUE ) {
$username='';
if (!phpCAS::isAuthenticated()) {
// 非常重要
// 构造登录返回的url
$url = phpCAS::$_PHPCAS_CLIENT->getServerLoginURL();
$url = substr($url, 0, strpos($url, 'login?service='));
$url = $url . 'login?service=' . urlencode(dreferer());
//phpCAS::setServerLoginURL($url);
//phpCAS::forceAuthentication ();
showmessage ( '尚未登录,<a href="'.$url.'" >前去登录</a><script type="text/javascript">window.top.location.href="'.$url.'";</script>' );
}
// 获取用户
$username = phpCAS::getAttribute('cname');
$password = '';
$email = phpCAS::getAttribute('email');
// $result = userlogin($_GET['username'], $_GET['password'], $_GET['questionid'], $_GET['answer'], $this->setting['autoidselect'] ? 'auto' : $_GET['loginfield'], $_G['clientip']);
// 采用自己的登陆方法
$result = userloginCas ( $username, $email );
$uid = $result ['ucresult'] ['uid'];
// 后面继续按照discuz的流程即可
然后在/source/function/function_member.php
添加自己的userloginCas()
方法:
/**
* Login via Cas
* @param $member
* @param $cookietime
*/
function userloginCas($username, $email) {
$return = array ();
$merge = 0;
// 根据用户名获取用户信息
$db_user_info = DB::fetch_first("SELECT `uid`,`username`,`password`,`email` FROM ". DB::table('ucenter_members') ." WHERE `username`='$username' ");
if ($db_user_info) {
$return ['ucresult'] = array(
$db_user_info['uid'],
$db_user_info['username'],
$db_user_info['password'],
$db_user_info['email'],
$merge,
);
} else {
$return ['ucresult'] = array(0, '', '', '', 0);
}
if ($merge && $return ['ucresult'] ['uid'] > 0 || $return ['ucresult'] ['uid'] <= 0) {
$return ['status'] = 0;
return $return;
}
$member = getuserbyuid ( $return ['ucresult'] ['uid'], 1 );
if (! $member || empty ( $member ['uid'] )) {
$return ['status'] = - 1;
return $return;
}
$return ['member'] = $member;
$return ['status'] = 1;
if ($member ['_inarchive']) {
C::t ( 'common_member_archive' )->move_to_master ( $member ['uid'] );
}
if ($member ['email'] != $return ['ucresult'] ['email']) {
C::t ( 'common_member' )->update ( $return ['ucresult'] ['uid'], array (
'email' => $return ['ucresult'] ['email']
) );
}
return $return;
}
上述代码的作用是,当用户请求登录时,若用户未在cas服务器登录,直接引导用户跳转至cas-server进行登录,并记录当前页面url在验证成功之后返回。否则直接初始化当前cas登录的用户。
隐藏右上角的登录框
登录全部交给cas-server,这里就不再需要登录框了,直接找到/template/default/member/login_simple.htm
魔改消灭它。
登出处理
function on_logout() {
global $_G;
$ucsynlogout = $this->setting ['allowsynlogin'] ? uc_user_synlogout () : '';
if ($_GET ['formhash'] != $_G ['formhash']) {
$service = dreferer () ;
phpCAS::logoutWithRedirectService ( $service );
showmessage ( 'logout_succeed', dreferer (), array (
'formhash' => FORMHASH,
'ucsynlogout' => $ucsynlogout,
'referer' => rawurlencode ( dreferer () )
) );
}
clearcookies ();
$_G ['groupid'] = $_G ['member'] ['groupid'] = 7;
$_G ['uid'] = $_G ['member'] ['uid'] = 0;
$_G ['username'] = $_G ['member'] ['username'] = $_G ['member'] ['password'] = '';
$_G ['setting'] ['styleid'] = $this->setting ['styleid'];
if (defined ( 'IN_MOBILE' )) {
$service = dreferer () ;
phpCAS::logoutWithRedirectService ( $service );
showmessage ( 'location_logout_succeed_mobile', dreferer (), array (
'formhash' => FORMHASH,
'referer' => rawurlencode ( dreferer () )
) );
} else {
// 退出
$service = dreferer () ;
phpCAS::logoutWithRedirectService ( $service );
// 后面这个showmessage提示实际上已经不会执行了
// showmessage ( 'logout_succeed', dreferer (), array (
// 'formhash' => FORMHASH,
// 'ucsynlogout' => $ucsynlogout,
// 'referer' => rawurlencode ( dreferer () )
// ) );
}
}
问题小结
本地cookie登录
我这里登录全部交给cas-server进行处理,每次web请求都会向cas-server进行认证,本地cookie事实上已经没有关系了。如果想把流程改为 先尝试本地cookie登录,再认证cas-server,看起来可以减少中间的会话过程(一般cas-server会在另一台服务器上),提升页面响应速度。这种方案的流程为,在验证cas之前先尝试cookie登录,然后尝试cas认证,认证成功则写入cookie,然后初始化用户登录状态。麻烦的地方在于用户在其他cas更改了用户之后,如何同步响应修改cookie更改用户。
2. ### 用户登录行为记录 ###
登录行为记录可以考虑放在cas-server端,否则需要通过其他方式判断用户登录行为,比如额外的参数(或者curl?)
3. ### 管理后台登录和用户管理 ###
discuz管理员需要原生的discuz账号密码登录,cas用户的账号密码由另一个数据库进行统一管理,因此需要同步两个系统的用户账号和密码。
事实上,当CAS连接了N个系统的时候,如何同步管理这多个系统的账号和权限是个非常棘手的问题。
4. ### 文章参考 ###
CAS客户端整合(一) Discuz!的更多相关文章
- CAS客户端整合(二) Zabbix
Zabbix是一个强大的服务器/交换机监控应用,有zabbix-server, zabbix-client, zabbix-web 三部分.zabbix-web管理端是用php写的. 前文参考:CAS ...
- CAS客户端整合(四)-- Cacti
Cacti 是一套纯 lnmp 搭建的服务器监控系统,用 SNMP 抓取数据,RRDTool 绘制表格 登录流程 Cacti 的登录同样是先判断session,再尝试从 cookie 读取 sessi ...
- CAS客户端整合(三) Otrs
OTRS 是用Perl写的一个工单邮件系统,非常强大. 登录流程 流程图略过 otrs没有像 discuz 和 zabbix 类似的游客登录状态,这样处理起来逻辑分支少一些. 不过还是考虑用 otrs ...
- destoon 会员整合Ucenter/Discuz!/PHPWind教程
首先进入 Destoon网站后台 -〉会员管理 -〉模块设置 -〉会员整合 假如需要整合的主站地址为 http://www.abc.com 论坛为 http://bbs.abc.com 1.整合Uce ...
- CAS客户端与SpringSecurity集成
4. CAS客户端与SpringSecurity集成 4.1 Spring Security测试工程搭建 (1)建立Maven项目casclient_demo3 ,引入spring依赖和spring ...
- 品优购商城项目(六)CAS客户端与SpringSecurity集成
cas单点登录旨在解决传统登录模式session在分布式项目中共享登录信息的问题. 本文cas服务器使用 4.0版本,仅供学习参考.把 cas.war 直接部署在tomcat即可,这里有个固定的用户名 ...
- cas sso 整合记录
首先说明下,我使用的cas-server版本是4.2.1 整合过程中遇到的问题及解决方式如下 1.因为使用https的话证书是个麻烦事,所以启用http 修改cas-server-webapp下的ca ...
- CAS客户端和服务器配置https证书
关于如何生成https证书可以看这篇文章: java生成Https证书,及证书导入的步骤和过程 下面整理cas如何整合https: cas服务器端部署(TLS[https]) 1.生成证书: 参照ja ...
- CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记
CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 cas服务器的搭建 导出证书(1和2步骤是找了课程,随便写了一下存记录,不过对于自己测试不投入使用应该不影响) C:\Users\D ...
随机推荐
- [模板]RMQ(冲刺准备中)
洛谷P3865 注意:位运算一定要加括号!因为他的优先级没有加减法高: 注意在预处理的时候判断的是前一个区间是否完整,故 i+(1<<(j-1))-1<=n; 取logn时最好多加一 ...
- Excel 常用快捷键
Excel 常用快捷键 1. 移动整列 使用Shift快捷键可以快速移动整列:选中该列,当鼠标变成十字箭头时,按住Shift键,然后将该列移动到想要的位置. 2 绝对引用 使用F4快捷键可以快速设置绝 ...
- Oracle 用户验证日志
1.sysdba/sysoper 权限用户验证日志;2.非sysdba/sysoper 权限用户验证日志;3.关于sqlcode; 1.sysdba/sysoper 权限用户验证日志:在数据库设置了参 ...
- [C#] IEnumerable vs IQueryable
这篇博客将介绍IEnumerable和IQueryable之间的区别. 1. IQueryable是继承自IEnumerable接口的.所以IEnumerable能做的,IQueryable都能做. ...
- Go 笔记和疑问?
前言: 本文是学习<<go语言程序设计>> -- 清华大学出版社(王鹏 编著) 的2014年1月第一版 做的一些笔记 , 如有侵权, 请告知笔者, 将在24小时内删除, 转载请 ...
- Spring5源码解析-论Spring DispatcherServlet的生命周期
Spring Web框架架构的主要部分是DispatcherServlet.也就是本文中重点介绍的对象. 在本文的第一部分中,我们将看到基于Spring的DispatcherServlet的主要概念: ...
- java.lang.ClassNotFoundException: org.springframework.orm.hibernate3.LocalSessionFactoryBean
Caused by: java.lang.ClassNotFoundException: org.springframework.orm.hibernate3.LocalSessionFactoryB ...
- 设置express ejs模板的后缀名html
如果使用jade或者ejs模板引擎的话 模板文件的格式为ejs或者jade ,有时候需要将后缀名修改为 html格式的. app.set('view engine','ejs'); app.engin ...
- STL 中的map 与 hash_map的理解
可以参考侯捷编著的<STL源码剖析> STL 中的map 与 hash_map的理解 1.STL的map底层是用红黑树存储的,查找时间复杂度是log(n)级别: 2.STL的hash_ma ...
- python二维码操作:QRCode和MyQR入门
1.QRCode QRCode最简单的使用 import qrcode qrcode.make("第一个二维码").get_image().show() 根据文本生成二维码并且直接 ...