263 lines
10 KiB
PHP
Executable File
263 lines
10 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* 共享账号登录;支持限定账户,部门,权限组;
|
|
*
|
|
* 1. 引入代码调用;(会引入整套kod的库; 侵入性; 函数名重名用命名空间处理)
|
|
* include('./config/config.php');
|
|
* $user = Action('user.sso')->check('adminer');
|
|
*
|
|
* 2. 通用CAS模式单点登录; (可跨站点跨服务器,不同服务之间调用); 可用其他语言实现类似逻辑;
|
|
* include('./app/api/KodSSO.class.php');
|
|
* $user = KodSSO::check('user:admin',$host=''); // 不同站需要传入kod站点的名称; 不传默认认为和当前kod在同一站点下;
|
|
*
|
|
* 权限检测支持:
|
|
* 1. 指定插件名, 权限同该插件配置用户权限
|
|
* 2. 指定用户: 空字符串或user:all 所有登录用户; user:admin系统管理员
|
|
* 3. 指定权限详情: {"user":"1,3","group":"1","role":"1,2"}; 同插件权限设置指定:用户,部门,角色
|
|
*
|
|
* 流程:
|
|
* 1. 有cookie kodTokenApi; 请求kod的认证接口; 返回[ok] 则继续;
|
|
* 2. 没有cookie kodTokenApi则跳转到kod登录界面; kod登录成功则带上kodToken跳转到该应用url; 再次验证kodToken成功则完成;
|
|
*/
|
|
|
|
@ini_set("display_errors","on");
|
|
@error_reporting(E_ALL^E_NOTICE^E_WARNING^E_DEPRECATED^E_STRICT);
|
|
class KodSSO{
|
|
public static function check($appName="",$host=""){
|
|
if(!$host){$host = self::appHost();}
|
|
$urlInfo = parse_url(self::thisUrl());
|
|
$key = 'kodTokenApi';
|
|
$keyCookie = 'kodTokenApi-'.substr(md5($urlInfo['path']),0,5);
|
|
$token = isset($_COOKIE[$keyCookie]) ? $_COOKIE[$keyCookie] : '';
|
|
$token = isset($_GET[$key]) ? $_GET[$key] : $token;
|
|
$userInfo = self::checkToken($appName,$host,$token);
|
|
if( $token && $userInfo){
|
|
if(isset($_GET[$key])){ // 首次登录成功跳转回来;
|
|
setcookie($keyCookie,$token, time()+3600*24,self::thisPathUrl(),false,false,true);
|
|
// 跳转到之前url; 去除url带入的token;
|
|
$linkBefore = self::urlRemoveKey(self::thisUrl(),$key);
|
|
header('Location: '.$linkBefore);exit;
|
|
}
|
|
return $userInfo;
|
|
}
|
|
|
|
$link = rawurlencode(self::thisUrl());
|
|
$url = $host.'?user/sso/apiLogin&appName='.$appName.'&callbackUrl='.$link;
|
|
header('Location: '.$url);exit;
|
|
}
|
|
private static function checkToken($appName,$host,$token){
|
|
if(!$token) return false;
|
|
$timeStart = microtime(true);
|
|
$uri = 'user/sso/apiCheckToken&accessToken='.$token.'&appName='.$appName;
|
|
$cacheKey = md5($uri);
|
|
$cacheData = self::cacheGet($cacheKey);
|
|
if(is_array($cacheData)){
|
|
self::cacheSet($cacheKey,$cacheData,3600);
|
|
return $cacheData;
|
|
}
|
|
|
|
$res = '';
|
|
$phpBin = self::phpBin();
|
|
if($phpBin && function_exists('shell_exec')){
|
|
$BASIC_PATH = str_replace('\\','/',dirname(dirname(dirname(__FILE__)))).'/';
|
|
$command = $phpBin.' '.$BASIC_PATH.'index.php '.escapeshellarg($uri);
|
|
$res = shell_exec($command);
|
|
}
|
|
if(!$res || substr(trim($res),0,1) != '{' ){ // 避免命令行调用返回错误的问题;
|
|
$context = stream_context_create(array(
|
|
'http' => array('timeout' => 2,'method'=>"GET"),
|
|
"ssl" => array("verify_peer"=>false,"verify_peer_name"=>false)
|
|
));
|
|
$res = file_get_contents($host.'?'.$uri,false,$context);
|
|
}
|
|
// var_dump(microtime(true) - $timeStart,$host.'?'.$uri,$res);exit;
|
|
$userInfo = @json_decode($res,true);
|
|
if( $userInfo && is_array($userInfo) ){
|
|
if(isset($userInfo['code']) && $userInfo['code'] == '10001') return false;
|
|
self::cacheSet($cacheKey,$userInfo,3600);
|
|
return $userInfo;
|
|
}
|
|
if(!strstr($res,'[error]:')){echo $res;exit;}
|
|
return false;
|
|
}
|
|
|
|
|
|
// 获取当前php执行目录;
|
|
private static function phpBin(){
|
|
if(defined('PHP_BINARY') && @file_exists(PHP_BINARY)){
|
|
$php = str_replace('-fpm','',PHP_BINARY);
|
|
if(@file_exists($php)) return $php;
|
|
}
|
|
if(!defined('PHP_BINDIR')) return false; // PHP_BINDIR,PHP_BINARY
|
|
$includePath = get_include_path();// php_ini_loaded_file();//php.ini path;
|
|
$includePath = substr($includePath,strpos($includePath,'/'));
|
|
|
|
$isWindow = strtoupper(substr(PHP_OS, 0,3)) === 'WIN';
|
|
$binFile = $isWindow ? 'php.exe':'php';
|
|
$checkPath = array(
|
|
PHP_BINDIR.'/',
|
|
dirname(dirname($includePath)).'/bin/',
|
|
dirname(dirname(dirname($includePath))).'/bin/',
|
|
);
|
|
foreach ($checkPath as $path) {
|
|
if(@file_exists($path.$binFile)) return $path.$binFile;
|
|
}
|
|
return 'php';
|
|
}
|
|
|
|
private static function urlRemoveKey($url,$key){
|
|
$parse = parse_url($url);
|
|
parse_str($parse['query'],$get);
|
|
unset($get[$key]);
|
|
$query = http_build_query($get);
|
|
$query = $query ? '?'.$query : '';
|
|
$port = (isset($parse['port']) && $parse['port'] != '80' ) ? ':'.$parse['port']:'';
|
|
return $parse['scheme'].'://'.$parse['host'].$port.$parse['path'].$query;
|
|
}
|
|
public static function thisUrl(){
|
|
return rtrim(self::host(),'/').'/'.ltrim($_SERVER['REQUEST_URI'],'/');
|
|
}
|
|
public static function thisPathUrl(){
|
|
$uriInfo = parse_url(self::thisUrl());
|
|
$uriPath = dirname($uriInfo['path']);
|
|
if(substr($uriPath,-1) == '/'){$uriPath = $uriInfo['path'];}
|
|
return '/'.trim($uriPath,'/');
|
|
}
|
|
public static function appHost(){
|
|
$BASIC_PATH = str_replace('\\','/',dirname(dirname(dirname(__FILE__)))).'/';
|
|
$WEB_ROOT = self::webrootPath($BASIC_PATH);
|
|
$WEB_URI = str_replace($WEB_ROOT,'',$BASIC_PATH);
|
|
|
|
// 有软连接情况处理;
|
|
if(substr($WEB_ROOT,0,strlen($BASIC_PATH)) != $WEB_ROOT ||
|
|
substr($BASIC_PATH,0,strlen($WEB_ROOT)) != $WEB_ROOT){
|
|
$WEB_URI = '';
|
|
$DOCUMENT_URI = isset($_SERVER["DOCUMENT_URI"]) ? $_SERVER["DOCUMENT_URI"]:'';
|
|
$pose = strpos($DOCUMENT_URI,'/plugins/');
|
|
if($pose >= 0){
|
|
$WEB_URI = substr($DOCUMENT_URI,1,$pose);
|
|
}
|
|
}
|
|
return self::host().$WEB_URI; //程序根目录
|
|
}
|
|
//解决部分主机不兼容问题
|
|
public static function webrootPath($basicPath){
|
|
$DOCUMENT_URI = isset($_SERVER["DOCUMENT_URI"]) ? $_SERVER["DOCUMENT_URI"]:'';
|
|
$SCRIPT_FILENAME = isset($_SERVER["SCRIPT_FILENAME"]) ? $_SERVER["SCRIPT_FILENAME"]:'';
|
|
$index = self::pathClear($basicPath.'index.php');
|
|
$uri = self::pathClear($DOCUMENT_URI);
|
|
// 兼容 index.php/explorer/list/path; 路径模式;
|
|
if($uri){//DOCUMENT_URI存在的情况; test.php ...统一化;
|
|
$uri = dirname($uri).'/index.php';
|
|
}
|
|
if( substr($index,- strlen($uri) ) == $uri){
|
|
$path = substr($index,0,strlen($index)-strlen($uri));
|
|
return rtrim($path,'/').'/';
|
|
}
|
|
$uri = self::pathClear($_SERVER["SCRIPT_NAME"]);
|
|
if( substr($index,- strlen($uri) ) == $uri){
|
|
$path = substr($index,0,strlen($index)-strlen($uri));
|
|
return rtrim($path,'/').'/';
|
|
}
|
|
|
|
// 子目录sso调用情况兼容;
|
|
if($SCRIPT_FILENAME && $DOCUMENT_URI){
|
|
$index = self::pathClear($SCRIPT_FILENAME);
|
|
$uri = self::pathClear($DOCUMENT_URI);
|
|
// 兼容 index.php/test/todo 情况;
|
|
if( strstr($uri,'.php/')){
|
|
$uri = substr($uri,0,strpos($uri,'.php/')).'.php';
|
|
}
|
|
if( substr($index,- strlen($uri) ) == $uri){
|
|
$path = substr($index,0,strlen($index)-strlen($uri));
|
|
return rtrim($path,'/').'/';
|
|
}
|
|
}
|
|
return str_replace('\\','/',$_SERVER['DOCUMENT_ROOT']);
|
|
}
|
|
public static function host(){
|
|
$httpType = 'http';
|
|
if(
|
|
(!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off') ||
|
|
(!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) ||
|
|
(!empty($_SERVER['HTTP_SSL']) && $_SERVER['HTTP_SSL'] == 1) ||
|
|
|
|
(!empty($_SERVER['HTTP_X_HTTPS']) && strtolower($_SERVER['HTTP_X_HTTPS']) !== 'off') ||
|
|
(!empty($_SERVER['HTTP_X_SCHEME']) && $_SERVER['HTTP_X_SCHEME'] == 'https') ||
|
|
(!empty($_SERVER['HTTP_X_SSL']) && ($_SERVER['HTTP_X_SSL'] == 1 || strtolower($_SERVER['HTTP_X_SSL']) == 'yes')) ||
|
|
(!empty($_SERVER['HTTP_CF_VISITOR']) && strpos($_SERVER['HTTP_CF_VISITOR'], 'https') !== false) ||
|
|
(!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) && $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] == 'https') ||
|
|
(!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
|
|
){
|
|
$httpType = 'https';
|
|
}
|
|
|
|
$port = (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] !='80') ? ':'.$_SERVER['SERVER_PORT']:'';
|
|
if($httpType == 'https' && $port == ':443'){$port = '';} // 忽略https 443端口;
|
|
$host = $_SERVER['SERVER_NAME'].$port;
|
|
if(isset($_SERVER['HTTP_HOST'])){$host = $_SERVER['HTTP_HOST'];}
|
|
if(isset($_SERVER['HTTP_HOST']) && $port && !strstr($_SERVER['HTTP_HOST'],':')){
|
|
//$host = $_SERVER['HTTP_HOST'].$port;// 反向代理后默认使用host
|
|
}
|
|
if(isset($_SERVER['HTTP_X_FORWARDED_HOST'])){//proxy
|
|
$hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']);
|
|
$host = trim($hosts[0]);
|
|
}else if(isset($_SERVER['HTTP_X_FORWARDED_SERVER'])){
|
|
$host = $_SERVER['HTTP_X_FORWARDED_SERVER'];
|
|
}
|
|
$host = str_replace(array('<','>'),'_',$host);// 安全检测兼容(xxs)
|
|
return $httpType.'://'.trim($host,'/').'/';
|
|
}
|
|
public static function pathClear($path){
|
|
$path = str_replace('\\','/',trim($path));
|
|
$path = preg_replace('/\/+/', '/', $path);
|
|
if (strstr($path,'../')) {
|
|
$path = preg_replace('/\/\.+\//', '/', $path);
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
// 记录缓存写入的key; 退出登陆时清除;
|
|
private static function cacheSetKey($key){
|
|
if(!isset($_COOKIE['KOD_SESSION_ID']) || !$key){return;}
|
|
$ssoKey = 'KOD_SSO_CACHE_KEY';
|
|
$cookie = isset($_COOKIE[$ssoKey]) ? $_COOKIE[$ssoKey] : '';
|
|
$urlInfo = parse_url(self::appHost());
|
|
$keyArr = explode(',',$cookie);
|
|
if(in_array($key,$keyArr)){return;}
|
|
|
|
$keyArr[] = $key;
|
|
setcookie($ssoKey,implode(',',$keyArr), time()+3600*24,$urlInfo['path'],false,false,true);
|
|
}
|
|
|
|
// 缓存读写处理;
|
|
public static function cacheSet($key, $value, $cacheTime = 3600){
|
|
$file = self::cacheFile($key);
|
|
$content = "<?php exit;?>".serialize($value);
|
|
if(file_put_contents($file,$content,LOCK_EX)){
|
|
@touch($file,intval(time() + $cacheTime));
|
|
clearstatcache();
|
|
self::cacheSetKey($key);
|
|
return true;
|
|
}
|
|
@unlink($file);
|
|
return false;
|
|
}
|
|
public static function cacheGet($key){
|
|
$file = self::cacheFile($key);
|
|
if(file_exists($file) && filemtime($file) < time()){
|
|
@unlink($file);return false;
|
|
}
|
|
$str = @file_get_contents($file);
|
|
$str = substr($str,strlen("<?php exit;?>"));
|
|
return unserialize($str);
|
|
}
|
|
public static function cacheFile($key){
|
|
$BASIC_PATH = str_replace('\\','/',dirname(dirname(dirname(__FILE__)))).'/';
|
|
$logPath = $BASIC_PATH.'data/temp/_cache/';
|
|
@mkdir($logPath, 0777, true);
|
|
|
|
$key = str_replace(array("/",'\\','?'),"_",$key);
|
|
return $logPath."cache_api_".$key.'.php';
|
|
}
|
|
} |