Yii框架应用程序整合Ucenter实现同步注册、登录和退出等

阅读更多

 

如今很多网站都要整合论坛程序,而康盛的Discuz系列产品往往是首选。然后就有了整合用户的需要,康盛提供了Ucenter架构,方便对不同的应用程序进行单点登录整合。

 

进来我尝试将ucenter整合到Yii网站中,获得了成功,虽然登录同步程序不是很妥当,基本使用没有问题了。我将继续改进。下面说说步骤:

 

  1. 下载安装ucenter和discuz,我使用的是ucenter1.6和discuz7.2,由于7.2自带的uc_client是旧版本,所以需要覆盖一下1.6版本。
  2. 复制一份uc_client文件夹到 protected/vendors/下,然后建立一个ucenter.php文件,内容如下:
     可以看到这里只是包含了两个文件。然后打开yii的主配置文件 protected/config/main.php,加入ucenter的一些全局变量的设置:
    dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    
    .....
    请根据你的情况修改上面的数据库名等设置。
  3. 实现同步注册,我采用的是定义了表单RegisterForm来处理用户的注册,下面是一个典型的注册表单及其验证和业务逻辑代码:
    20, 'min'=>5),
    			// 用户名唯一性验证
    			//array('username', 'unique','caseSensitive'=>false,'className'=>'user','message'=>'用户名"{value}"已经被注册,请更换'),
    			array('username', 'checkname'),
    			// 密码一致性验证
    			array('repassword', 'compare', 'compareAttribute'=>'password','message'=>'两处输入的密码并不一致'),
    			// 电子邮件验证
    			array('email', 'email'),
    			// 电子邮件唯一性
    			//array('email', 'unique','caseSensitive'=>false,'className'=>'user','message'=>'电子邮件"{value}"已经被注册,请更换'),
    			array('email', 'checkemail'),
    			//array('birthday', 'match', 'pattern'=>'%^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$%', 'allowEmpty'=>true, 'message'=>'生日必须是年-月-日格式'),
    			//array('mobile', 'length', 'max'=>11, 'min'=>11, 'tooLong'=>'手机号码错误','tooShort'=>'手机号码错误'),
                array('verifyCode', 'captcha', 'allowEmpty'=> false),
    		);
    	}
    
    	public function checkname($attribute,$params)
    	{
    		//ucenter
    		Yii::import('application.vendors.*');
    		include_once 'ucenter.php';
    		$flag = uc_user_checkname($this->username);
    		
    		switch($flag)
    		{
    			case -1:
                	$this->addError('username', '用户名不合法');
    				break;
    			case -2:
    				$this->addError('username','包含不允许注册的词语');
    				break;
    			case -3:
    			 	$this->addError('username','用户名已经存在');
    				break;
    		}
    	}
    	
    	public function checkemail($attribute,$params)
    	{
    		//ucenter
    		Yii::import('application.vendors.*');
    		include_once 'ucenter.php';
    		$flag = uc_user_checkemail($this->email);
    
    		switch($flag)
    		{
    			case -4:
                	$this->addError('email', 'Email 格式有误');
    				break;
    			case -5:
    				$this->addError('email','Email 不允许注册');
    				break;
    			case -6:
    			 	$this->addError('email','该 Email 已经被注册');
    				break;
    		}
    	}
    
    	/**
    	 * Declares attribute labels.
    	 */
    	public function attributeLabels()
    	{
    		return array(
                'username'=>'设定用户名',
                'password'=>'设定密码',
    			'repassword'=>'再次输入密码',
    		    'email'=>'电子邮件地址',
    		    'mobile'=>'手机号码',
                'verifyCode'=>'验证码',
    		);
    	}
    
    	/**
    	 * 注册用户
    	 * @return boolean whether register is successful
    	 */
    	public function register($uid)
    	{
    		//ucenter
    		Yii::import('application.vendors.*');
    		include_once 'ucenter.php';
    		$uid = uc_user_register($this->username, $this->password, $this->email);
    		if($uid>0)
    		{
    			$model = new user;
    			$model->attributes = $_POST['RegisterForm'];
    			$model->password = md5($_POST['RegisterForm']['password']);
    			$model->id = $uid;
    			
    			return $model->save();
    		}
    	}
    }
    
    我们看看上面的代码,调用了uc_user_checkname和uc_user_checkemail完成了用户名和email的验证,然后调用了 uc_user_register将用户注册到ucenter,成功后,再注册到Yii应用。
  4. 实现用户登录,典型的Yii应用使用 CUserIdentity来实现登录,我们要做的就是继承它,实现自己的验证逻辑:
    username, $this->password);
    		if($uid > 0)
    		{
    			$user = user::model()->findByPk($uid);
    			
    			if($user == null)//说明网站数据库中没有,而ucenter中有这个用户,添加用户
    			{
    				$user = new user;
    				$user->username = $username;
    				$user->password = md5($password);
    				$user->email = $email;
    				$user->id = $uid;
    				$user->save();
    				
    				$user->refresh();
    			}
    			
                $this->username = $user->username;
                $this->id = $user->id;
    			
                $user->last_login_time = $user->this_login_time;
                $user->this_login_time = time();
                $user->last_login_ip = $user->this_login_ip;
                $user->this_login_ip = Yii::app()->getRequest()->userHostAddress;
                $user->save(); 
    	        
    			$this->errorCode=self::ERROR_NONE;
    		}
    		elseif($uid == -1)
    		{
    			$this->errorCode=self::ERROR_USERNAME_INVALID;
    		}
    		elseif($uid == -2)
    		{
    			$this->errorCode=self::ERROR_PASSWORD_INVALID;
    		}
    	    
    		return !$this->errorCode;
    	}
        
        public function getId()
    	{
    		return $this->id;
    	}
    }
    请根据自己的应用情况进行修改即可,这里我们不需要对Yii的用户登录做任何修改。
  5. 然后我们修改 SiteController/actionLogin 方法,将同步登录其他应用的js输出到浏览器:
    /**
    	 * Displays the login page
    	 */
    	public function actionLogin()
    	{
    		$model=new LoginForm;
    
    		// if it is ajax validation request
    		if(isset($_POST['ajax']) && $_POST['ajax']==='login-form')
    		{
    			echo CActiveForm::validate($model);
    			Yii::app()->end();
    		}
    
    		// collect user input data
    		if(isset($_POST['LoginForm']))
    		{
    			$model->attributes=$_POST['LoginForm'];
    			// validate user input and redirect to the previous page if valid
    			if($model->validate() && $model->login())
    			{
    				//ucenter
    				Yii::import('application.vendors.*');
    				include_once 'ucenter.php';
    				$script = uc_user_synlogin(Yii::app()->user->id);
    				$this->render('loginsuc', array(
    					'script' => $script,
    				));
    				Yii::app()->end();
    			}
    		}
    		// display the login form
    		$this->render('login',array('model'=>$model));
    	}
    简单的loginsuc.php视图文件:
    
    
    
    	
    
    
    layout = 'none';
    echo $script; 
    ?>
    
    登录成功,正在返回登录前页面...
    
    
  6. 继续修改 SiteController/actionLogout方法,实现同步退出:
    /**
    	 * Logs out the current user and redirect to homepage.
    	 */
    	public function actionLogout()
    	{
    		Yii::app()->user->logout();
    		//ucenter
    		Yii::import('application.vendors.*');
    		include_once 'ucenter.php';
    		$script = uc_user_synlogout();
    		$this->render('logoutsuc', array(
    			'script' => $script,
    		));
    		Yii::app()->end();
    	}
        
    简单的logoutsuc.php视图文件:
    
    
    
    	
    
    
    layout = 'none';
    echo $script; 
    ?>
    
    退出成功,正在返回首页...
    
    

  7. 进行到这里,我们已经实现了整合ucenter的登录和注册了,这样ucenter中有的用户,可以登录到yii应用,yii应用也可以注册用户到ucenter了。但是这还没有完成,我们需要的是在discuz中用户登录时,也同步登录yii应用,退出亦然,那么我们需要实现 Yii应用的 api/uc.php 这个接口程序。由于我们要用到Yii的框架资源,所以我没有采用硬编码的方式实现这个接口,而是创建了一个UcApplication类完成这个任务,继续往下看。
  8. 首先建立 api/uc.php 入口文件,代码如下:
    run();
    
    这里可以看到,这个脚本和标准的index.php是一样的,只是使用了不同的Application类。我们接着看这个类。
  9. 建立 protected/components/UcApplication.php类文件:
    parseRequest();
        }
    	
    	private function parseRequest()
    	{
    		$_DCACHE = $get = $post = array();
    
    		$code = @$_GET['code'];
    		parse_str($this->_authcode($code, 'DECODE', UC_KEY), $get);
    		if(MAGIC_QUOTES_GPC) {
    			$get = $this->_stripslashes($get);
    		}
    		
    		$timestamp = time();
    		if($timestamp - $get['time'] > 3600) {
    			//exit('Authracation has expiried');
    		}
    		if(empty($get)) {
    			exit('Invalid Request');
    		}
    		$action = $get['action'];
    		
    		require_once 'xml.class.php';
    		$post = xml_unserialize(file_get_contents('php://input'));
    		Yii::log($get, 'debug');
    		Yii::log($post, 'debug');
    		$_GET = $get;
    		$_POST = $post;
    		
    		$this->route = $this->defaultController .'/'. $action;
    		
    		if(!in_array($action, array('test', 'deleteuser', 'renameuser', 'gettag', 'synlogin', 'synlogout', 'updatepw', 'updatebadwords', 'updatehosts', 'updateapps', 'updateclient', 'updatecredit', 'getcreditsettings', 'updatecreditsettings'))) 
    		{
    			exit(API_RETURN_FAILED);
    		}
    	}
        
        public function processRequest()
    	{
    		$this->runController($this->route);
    	}
    	
    	private function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
    		$ckey_length = 4;
    	
    		$key = md5($key ? $key : UC_KEY);
    		$keya = md5(substr($key, 0, 16));
    		$keyb = md5(substr($key, 16, 16));
    		$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
    	
    		$cryptkey = $keya.md5($keya.$keyc);
    		$key_length = strlen($cryptkey);
    	
    		$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    		$string_length = strlen($string);
    	
    		$result = '';
    		$box = range(0, 255);
    	
    		$rndkey = array();
    		for($i = 0; $i <= 255; $i++) {
    			$rndkey[$i] = ord($cryptkey[$i % $key_length]);
    		}
    	
    		for($j = $i = 0; $i < 256; $i++) {
    			$j = ($j + $box[$i] + $rndkey[$i]) % 256;
    			$tmp = $box[$i];
    			$box[$i] = $box[$j];
    			$box[$j] = $tmp;
    		}
    	
    		for($a = $j = $i = 0; $i < $string_length; $i++) {
    			$a = ($a + 1) % 256;
    			$j = ($j + $box[$a]) % 256;
    			$tmp = $box[$a];
    			$box[$a] = $box[$j];
    			$box[$j] = $tmp;
    			$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    		}
    	
    		if($operation == 'DECODE') {
    			if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
    				return substr($result, 26);
    			} else {
    					return '';
    				}
    		} else {
    			return $keyc.str_replace('=', '', base64_encode($result));
    		}
    	
    	}
    
    	private function _stripslashes($string) {
    		if(is_array($string)) {
    			foreach($string as $key => $val) {
    				$string[$key] = $this->_stripslashes($val);
    			}
    		} else {
    			$string = stripslashes($string);
    		}
    		return $string;
    	}
    }
    
    这里可以看到,主要逻辑是processRequest方法,实现了ucenter通知的解码、调用相应的控制器和动作实现操作。
  10. 然后建立 protected/controller/UcController.php文件,代码如下:
    appdir = Yii::app()->basePath . '/vendors/';
    		return parent::beforeAction($action);
    	}
    	
    	public function actionTest() {
    		echo API_RETURN_SUCCEED;
    	}
    
    	public function actionDeleteuser() {
    		$uids = explode(',', str_replace("'", '', $_GET['ids']));
    		!API_DELETEUSER && exit(API_RETURN_FORBIDDEN);
    		
    		$users = user::model()->findAllByPk($uids);
    		foreach($users as $user)
    		{
    			$user->delete();
    		}
    
    		echo API_RETURN_SUCCEED;
    	}
    
    	public function actionRenameuser() {
    		$uid = $_GET['uid'];
    		$usernameold = $_GET['oldusername'];
    		$usernamenew = $_GET['newusername'];
    		if(!API_RENAMEUSER) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		
    		$user = user::model()->findByPk($uid);
    		if($user !== null)
    		{
    			$user->username = $usernamenew;
    			if($user->save(false))
    				echo API_RETURN_SUCCEED;
    			else
    				echo API_RETURN_FAILED;
    		}
    	}
    
    	public function actionGettag() {
    		$name = $_GET['id'];
    		if(!API_GETTAG) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		
    		$echo = array();
    		echo $this->_serialize($return, 1);
    	}
    
    	public function actionSynlogin() {
    		$uid = $_GET['uid'];
    		$username = $_GET['username'];
    		if(!API_SYNLOGIN) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		
    		$identity=new UcUserIdentity($username);
    
    		if($identity->authenticate())
    		{
    			Yii::app()->user->login($identity, 0);
    		}
    
    		header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
    		//$_SESSION[Yii::app()->user->stateKeyPrefix.'__id'] = $uid;
    		//$_SESSION[Yii::app()->user->stateKeyPrefix.'__name'] = $username;
    	}
    
    	public function actionSynlogout() {
    		if(!API_SYNLOGOUT) {
    			echo API_RETURN_FORBIDDEN;
    		}
    
    		//note 同步登出 API 接口
    		header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
    		Yii::app()->user->logout();
    	}
    
    	public function actionUpdatepw() {
    		if(!API_UPDATEPW) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		$username = $_GET['username'];
    		$password = $_GET['password'];
    		
    		$user = user::model()->findByAttributes(array('username'=>$username));
    		if($user !== null)
    		{
    			$user->password = md5($password);
    			if($user->save())
    				echo API_RETURN_SUCCEED;
    			else
    				echo API_RETURN_FAILED;
    		}
    		else
    			echo API_RETURN_FAILED;
    	}
    
    	public function actionUpdatebadwords() {
    		if(!API_UPDATEBADWORDS) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		$cachefile = $this->appdir.'./uc_client/data/cache/badwords.php';
    		$fp = fopen($cachefile, 'w');
    		$data = array();
    		if(is_array($_POST)) {
    			foreach($_POST as $k => $v) {
    				$data['findpattern'][$k] = $v['findpattern'];
    				$data['replace'][$k] = $v['replacement'];
    			}
    		}
    		$s = "appdir.'./uc_client/data/cache/hosts.php';
    		$fp = fopen($cachefile, 'w');
    		$s = "appdir.'./uc_client/data/cache/apps.php';
    		$fp = fopen($cachefile, 'w');
    		$s = "basePath.'./config/main.php';
    		if(is_writeable($config_file)) {
    			$configfile = trim(file_get_contents($config_file));
    			$configfile = substr($configfile, -2) == '?>' ? substr($configfile, 0, -2) : $configfile;
    			$configfile = preg_replace("/define\('UC_API',\s*'.*?'\);/i", "define('UC_API', '$UC_API');", $configfile);
    			if($fp = @fopen($config_file, 'w')) {
    				@fwrite($fp, trim($configfile));
    				@fclose($fp);
    			}
    		}
    	
    		echo API_RETURN_SUCCEED;
    	}
    
    	public function actionUpdateclient() {
    		if(!API_UPDATECLIENT) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		$cachefile = $this->appdir.'./uc_client/data/cache/settings.php';
    		$fp = fopen($cachefile, 'w');
    		$s = "_serialize($credits);
    	}
    
    	public function actionUpdatecreditsettings() {
    		if(!API_UPDATECREDITSETTINGS) {
    			echo API_RETURN_FORBIDDEN;
    		}
    		echo API_RETURN_SUCCEED;
    	}
    	
    	private function _serialize($arr, $htmlon = 0) {
    		if(!function_exists('xml_serialize')) {
    			include_once 'xml.class.php';
    		}
    		echo xml_serialize($arr, $htmlon);
    	}
    }
    

    上面用到了xml.class.php这个类文件,可以在uc_client/lib目录找到。
    这里需要说明的是,actionSynlogin方法中,利用了我定义的特殊的UserIdentity来登录的,因为不需要提供密码。
  11. 再来看看最后一个类:
    username=$username;
    		$this->password='';
    	}
    	/**
    	 * Authenticates a user.
    	 * The example implementation makes sure if the username and password
    	 * are both 'demo'.
    	 * In practical applications, this should be changed to authenticate
    	 * against some persistent user identity storage (e.g. database).
    	 * @return boolean whether authentication succeeds.
    	 */
    	public function authenticate()
    	{
    		$user = user::model()->findByAttributes(array('username'=>$this->username));
    		
    		if($user == null)//说明网站数据库中没有,而ucenter中有这个用户,添加用户
    		{
    			//ucenter
    			Yii::import('application.vendors.*');
    			include_once 'ucenter.php';
    			list($uid, $username, $email) = uc_get_user($this->username);
    			if($uid)
    			{
    				$user = new user;
    				$user->username = $username;
    				$user->password = md5(rand(10000,99999));
    				$user->email = $email;
    				$user->id = $uid;
    				$user->save();
    				
    				$user->refresh();
    			}
    		}
    		
            $this->id = $user->id;
    		
            $user->last_login_time = $user->this_login_time;
            $user->this_login_time = time();
            $user->last_login_ip = $user->this_login_ip;
            $user->this_login_ip = Yii::app()->getRequest()->userHostAddress;
            $user->save(); 
    	    
    		$this->errorCode=self::ERROR_NONE;
    		
    		return !$this->errorCode;
    	}
        
        public function getId()
    	{
    		return $this->id;
    	}
    }
    可以看到,在这个认证类中,实现了对yii应用中没有的用户的建立操作。然后不需要对yii应用做任何特殊设置,就可以实现api接口了。
  12. 然后我们在ucenter中添加yii应用的设置,修改main.php中的相应设置,应该就可以实现ucenter的同步登录、注册、退出、删除用户、修改用户名等等功能了!这个实现方法相对很Yii,呵呵。

有什么问题,欢迎评论,和我联系。大家一起进步吧!

 

PS: 需要注意的是,整合了ucenter的Yii应用在部署时,需将 protected/vendors/uc_client/data/及其子目录、文件设为可写。否则将会有些奇怪的问题。

你可能感兴趣的:(PHP,Yii,ucenter,discuz)