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 = "".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("")); 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'; } }