YetiForceCRM-5.2.0集成OAuth2单点登录笔记

上周试用了一下YetiForceCRM,感觉还不错。为了便于内部推广,不让登录密码变成拦路虎,所以需要做一下YetiForceCRM和内部人员的单点登录。

一、大致思路

改造登录页面,增加单点登录入口;

二、具体细节

2.1 layouts/basic/modules/Users/Login.Default.tpl 增加单点登录信息

<script></script>之间增加以下代码。

        var  intervalCountDown = null;
    (function(){

        $('.fieldContainer').hide();
		$('.fieldContainer').after("<div id='iiosSSO'><span>如无操作, <span id=\"countDown\" style=\"color:#F1A325;\">5</span> 秒后将进入地听平台-统一认证!</h5></span><button  onclick='iiosLogin()' class='btn btn-block btn-primary'  type=\"button\">地听平台-统一认证</button><br><button onclick='accountLogin()' style='float: right;' class='btn btn-link' type='button'>账号密码登录</button></div>");

		var count = 5;
        intervalCountDown = setInterval(function(){
            count -=1;
            $('#countDown').html(count);
            if(count == 0){
                iiosLogin();
            }
        },1000);
    
})();

function iiosLogin()
{
    window.location.href='{$REDIRECT_URI}';
}
function accountLogin(){
    clearInterval(intervalCountDown);
    $('.fieldContainer').show();
    $('#iiosSSO').hide();
}

2.2 增加modules/Users/models/OAuth2Client.php

<?php
/**
 * Created by PhpStorm.
 * User: think
 * Date: 2020-02-27
 * Time: 16:26
 */

class Users_OAuth2Client_Model extends Vtiger_Module_Model{

    private $token_key = 'iios-cloud-token';

    public function getConf(){
        return array(
            'app_id'=>'iios',
            'client_id'=>'9488a',
            'client_secret'=>'$2a$10$47wh8swToBhNje/1BuMkS',
            'response_type'=>'code',
            'scope'=>'user_info',
            'authorizeEndpoint'=>'https://ww/console/#/connect/sso',
            'accessTokenEndpoint'=>'https://apauth/oauth2/token',
            'getUserInfoEndpoint'=>'https://api.auth/oauth2/userInfo',
            'logoutEndpoint'=>'https://am.cn/auth/sso/logout',
            'state'=>md5(uniqid(mt_rand(), true))
        );
    }
    public function getConfig(){
        $conf = $this->getConf();
        $conf['login_url'] = $this->getConf()['authorizeEndpoint'];
        return $conf;
    }

    /**
     * 获取URL参数值
     */
    public function getQueryStr($key)
    {
        $query_str = $_SERVER['QUERY_STRING'];
        parse_str($query_str,$query_str);
        $result = $query_str[$key];
        if(strlen($result)<1){
            throw new Exception(sprintf("获取URL QUERY_STRING :{%s}出错,请检查URL.",$result));
        }
        return $result;
    }
    public function postRequest($url,$header){
        $ch = curl_init();
        $defaultHeader = ['Accept: application/json, text/plain, */*' ];
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeader, $header));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $body = curl_exec($ch);
        curl_close($ch);
        return json_decode($body);
    }
    public function getRequest($url,$header){
        $ch = curl_init();
        $defaultHeader = ['Accept: application/json, text/plain, */*' ];
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge($defaultHeader, $header));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $body = curl_exec($ch);
        curl_close($ch);
        return json_decode($body);
    }
    public function getRedirectUri()
    {
        return sprintf('%s?app_id=%s&client_id=%s&response_type=%s&scope=%s&state=%s&redirect_uri=%s'
            , $this->getConf()['authorizeEndpoint']
            , $this->getConf()['app_id']
            , $this->getConf()['client_id']
            , $this->getConf()['response_type']
            , $this->getConf()['scope']
            , $this->getConf()['state']
            , urlencode((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://"         .         $_SERVER['HTTP_HOST'] .  '/index.php?module=Users&action=Login')
        );
    }
    /**
     * 根据authorizeEndpoint Redirect的Code,获取Token
     * @param $code
     */
    public function getToken($code)
    {
        $url = sprintf('%s?code=%s',$this->getConf()['accessTokenEndpoint'],$code);
        $result =  $this->postRequest($url,['Authorization: Basic '.base64_encode(sprintf('%s:%s',$this->getConf()['client_id'],$this->getConf()['client_secret']))]);
        $this->setSession($this->token_key,$result->{'access_token'});
        return $result->{'access_token'};
    }
    public function getUserName($code)
    {
        $token = $this->getToken($code);
        $result = $this->getRequest($this->getConf()['getUserInfoEndpoint'],[sprintf('Authorization: Bearer %s',$token)]);
        return $result->{'username'};
    }
    public function start_session()
    {
        if(session_status() !== PHP_SESSION_ACTIVE){
            session_start();
        }
    }
    public function setSession($key,$value)
    {   $this->start_session();
        $_SESSION[$key]=$value;
    }
    public function getSession($key)
    {
        $this->start_session();
        return $_SESSION[$key];
    }
    public function loginOut() {
        try{

            $token = $this->getSession($this->token_key);
            $logoutUrl =  $this->getConf()['logoutEndpoint'];
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL,$logoutUrl);
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json, text/plain, */*','Authorization: Bearer '.$token]);//设置header
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_exec($ch);
            curl_close($ch);
        }
        catch(Exception $e){

        }
    }

}

2.3 modules/Users/views/Login.php写入REDIRECT_URI view变量

process方法中增加两行代码:

$oauth = new Users_OAuth2Client_Model();
$viewer->assign('REDIRECT_URI', $oauth->getRedirectUri());

2.4 modules/Users/actions/Login.php 增加单点登录的代码

login() 函数刚开头加入以下代码:

		/**
		 * Oauth2自动登录
		 */
		$oauth = new Users_OAuth2Client_Model();
		$code = $request->getRaw('code');
		if(strlen($code)>4){
			$userName = $oauth->getUserName($code);
			$this->userRecordModel = Users_Record_Model::getCleanInstance('Users')->set('user_name', $userName);
			$this->userRecordModel->doLoginByUsername($userName);
			$this->userModel = App\User::getUserModel($this->userRecordModel->getId());
			
			$this->afterLogin($request);
			
			Users_Module_Model::getInstance('Users')->saveLoginHistory(strtolower($userName));
			header('location: index.php');
			return true;
		}

2.5 增加UserModel modules/Users/models/Record.php

新增函数:

	public function doLoginByUsername($userName)
	{
		$row = (new App\Db\Query())->select(['id', 'user_name','deleted'])->from('vtiger_users')->where(['or', ['user_name' => $userName], ['user_name' => strtolower($userName)]])->limit(1)->one();
               if($row == false){
                        die('未授权,禁止登录!');
                }

		$this->set('id', $row['id']);
		$userRecordModel = static::getInstanceFromFile($row['id']);
		if ('Active' !== $userRecordModel->get('status')) {
			\App\Log::info('Inactive user :' . $userName, 'UserAuthentication');
			return false;
		}
	}

2.6 暂时关闭CSRF ‘Invalid request – validate Write Access’检测

修改app/Request.php中的validateWriteAccess方法,如下:

		if (!$skipRequestTypeCheck && 'POST' !== $_SERVER['REQUEST_METHOD']) {
			//throw new \App\Exceptions\Csrf('Invalid request - validate Write Access');
		}

代码改造完成,经过测试可以正常登录。

代码于周末凌晨3点写完,只实现主要逻辑,质量不高,不要见怪。

分享到:更多 ()