上周试用了一下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点写完,只实现主要逻辑,质量不高,不要见怪。