有好幾個系統須要接入CAS,因此登陸模塊通通須要重構javascript
由於discuz原來的流程是驗證本身的一套用戶密碼體系,如今咱們須要將這個驗證過程放在 CAS-server ,而後經過綁定的 php-cas-client
來獲取登陸狀態。由這個登陸狀態來決定需不須要初始化用戶。php
簡(jian)單(nan)研究 Discuz! 的源碼,大體畫出它的登陸流程
html
咱們修改後的登陸流程,橙色爲須要修改的部分
java
這裏能夠根據本身的業務場景進行調整,大體的流程是同樣的。可能在是否本地cookie自動登陸這點會有區別,在文末總結裏會對這一點談談我我的的分析。web
畫好了流程圖,而後能夠開始動手寫代碼了。數據庫
根目錄下引入整個phpCAS客戶端/cas/
,新建一個CasClient.php
用來作一些初始化工做。api
/* * 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,問題解決。
cookie
phpCAS 客戶端是經過 session 來記錄isAuthenticated()
狀態,引入phpCAS的地方能夠獲取正確的session, 在後面的業務代碼中就爲null。聰明的你已經想到由於跟上面相同的緣由,全局的 session 變量在 Discuz 初始化的時候被清空了。所以對/source/class/discuz/discuz_application.php
進行以下修改session
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對象。
登陸過程當中有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 () ) // ) ); } }