dolia 发表于 2013-10-21 14:56:56

PHP实现跨域单点登录(CI框架)

本帖最后由 dolia 于 2013-10-21 14:58 编辑

最近公司需要制作单点登录系统,于是我一直在搜寻各种资料寻找最优化的单点登录制作方式,最后综合了各种想法,完成了单点登录系统,下面给大家分享下,我实现的单点登录的方式:

原文转自:www.W3PHP.com W3PHP

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。(百度百科)

我本人认为单点登录主要解决一下问题:

1. 一个用户账号,多站点通用;
2. 一个应用登录,其他应用同时登录;
3. 一个账号退出,其他应用账号同时退出;(非必须,此项不在单点登录的讨论范围之内,不过WEB单点登录,用到的还是挺多的);
4. 欢迎补充

PHP实现单点登录方法有多种:

1. 通过关系服务 如webservice 之类的;
2. 数据库共享session实现;
4. P3P跨站点 如ucenter phpcms 等;
5. 通过文件操作 SESSIONID
6. 欢迎补充

我使用的是第5种方法,下面简单说下实现:

一 登陆原理说明

我把SSO分为三个角色:

1. 客户端 – 指的是用户的浏览器
2. 代理端 – 指的是用户访问的网站;
3. 服务端 - 单点登录服务端,提供用户信息方;

同域实现SESSION共享非常容易,但是跨域实现网站和单点登录服务器共享SESSION,就需要做些手脚了;我是这样做的:


二. 过程说明:

登陆流程:

1. 第一次登陆某个站:

a) 站点生成随机字符串TOKEN,保存在cookie中,并携带加密过的APPID,APPKEYURL跳转到单点登录服务器;单点登录服务器则解密APPID 和APPKEY并去验证站点合法性;同时单点登录服务端会启动SESSION,并根据TOKEN在单点登录服务端的SESSION生成路径,同时生成以TOKEN为文件名的SESSION_TOKEN,把开始启动SESSION的PHPSSID,写入伪SESSION_TOKEN中,实现SESSION共享,然后使客户端跳转至原来的站点URL;

a) 用户输入用户名+密码,向用户验证中心发送登录请求

b) 当前登录站点,通过CURL请求,用户验证中心验证用户名,密码的合法性。如果验证通过,则返回用户信息,不通过则返回错误信息;

c)根据上一步的CURL请求返回的结果,当前子站对用户进行登陆处理,如果接受到错误,则进行错误处理;

2. 登陆状态下,用户转到另一子:

a) 通过本站cookie或session验证用户的登录状态:如验证通过,进入正常本站处理程序;不通过则重复第一次登陆步骤;


下面把代码贴上:(使用的是CI框架)


服务端:


<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

/**
* 单点登录控制器类
*
*/
class Sso extends CI_Controller {
      
      /**
         * session存储位置
         *
         * @var string
         */
      public $links_path;
      
    /**
   * 标识sessioin是否开启
   * @var boolean
   */
    protected $started=false;      
      
    /**
   * 当前应用
   * @var string
   */
    protected $broker = null;
      
      /**
         * 当前用户
         */      
         
      protected $user = null;

      /**
         * 构造函数
         */
      public function __construct()
      {
                parent::__construct();
                              
                $this->load->model('broker_model');
               
                $this->load->model('user_model');
               
                //如果创建连接函数没有开启,$link_path系统默认存储session目录
                //if (!function_exists('symlink')) $this->links_path = sys_get_temp_dir();
                $this->links_path = sys_get_temp_dir();
      
      }
      
      public function index()
      {
                exit('1');
      }

      
    /**
   * 登录
   */

    public function login()
    {

      $this->_session_start();
               
                $username = $this->input->post('username');
               
                $password = $this->input->post('password');
               
      if (empty($username)) $this->failLogin("no_user_name");
               
      if (empty($password)) $this->failLogin("no_password");

                //数据库验证
      
                $info = $this->user_model->_web_login($this->input->post('username'),$this->input->post('password'));
               
                if (isset($info['user'])&&$info['user']!='')
                {
                        $_SESSION['user'] = $info['user'];
                        
                        $this->info();
                        
                }else{
                        
                        $this->failLogin($info['error']);
                }
               
    }
      
      
    /**
   * PC登录
   */

    public function pc_login()
    {

      $this->_session_start();
               
                $cihiuserno = $this->input->post('cihiuserno');
               
                $cihikey = $this->input->post('cihikey');
               
      if (empty($cihiuserno)) $this->failLogin("no_cihiuserno");
               
      if (empty($cihikey)) $this->failLogin("no_cihikey");

                //数据库验证
      
                $info = $this->user_model->_pc_login($this->input->post('cihiuserno'),$this->input->post('cihikey'));
               
               
                if (isset($info['user'])&&$info['user']!='')
                {
                        $_SESSION['user'] = $info['user'];
                        $this->info();
                        
                }else{
                        
                        $this->failLogin($info['error']);
                }
               
    }               
      
    /**
   * 退出
   */
    public function logout()
    {
            header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
               
            $this->load->model('broker_model');
               
                $brokers = $this->broker_model->get_all_broker();
               
                $res='';
                foreach ($brokers as $k=>$v)
                {
                        if (trim($v['api_url'])!=''){
                              $tmp_s = strstr($v['api_url'], '?') ? '&' : '?';
                              $res .= '<script type="text/javascript" src="'.$v['api_url'].$tmp_s.'&time='.time().' reload="1"></script>';                              
                        }
                }
               
               
       $this->_session_start();
       unset($_SESSION['user']);
         echo $res;

    }
      
      
    /**
   * 输出用户信息
   */
    public function info()
    {
      $this->_session_start();
                //如果不存在登陆用户 返回提示
            if (!isset($_SESSION['user'])) $this->failLogin("Not logged in");
               
                echo json_encode($_SESSION['user']);exit();

    }      
      
      
    /**
   * 连接session
   */
    public function attach()
    {
            //开启回话
      $this->_session_start();

                //检验broker
                $broker = $this->input->get_post('broker');
      if (empty($broker)) $this->fail("No broker specified");
                //检验token
                $token = $this->input->get_post('token');
      if (empty($token)) $this->fail("No token specified");
                //检验校验码
                $checksum = $this->input->get_post('checksum');
      if (empty($checksum) || $this->generateAttachChecksum($broker, $token) != $checksum) $this->fail("Invalid_checksum");
                //如果没有设置session存储位置
      if (!isset($this->links_path)) {
               
                //拼接session存储文件
                $link = (session_save_path() ? session_save_path() : sys_get_temp_dir()) . "/sess_" . $this->generateSessionId($broker, $token);
                        //如果sessioin文件不存在 把本文件链接到系统的session_id上
                if (!file_exists($link)) $attached = symlink('sess_' . session_id(), $link);
                        //如果没有链接成功,报错
                if (!$attached) trigger_error("Failed to attach; Symlink wasn't created.", E_USER_ERROR);
      } else {
                //指定session路径存放session
                $link = "{$this->links_path}/" . $this->generateSessionId($broker, $token);
                if (!file_exists($link)) $attached = file_put_contents($link, session_id());
                if (!$attached) trigger_error("Failed to attach; Link file wasn't created.", E_USER_ERROR);
                }
                //跳转至broker
                $redirect = $this->input->get_post('redirect');
      if (isset($redirect)) {
            header("Location: " . $redirect, true, 307);
            exit;      
      }

      // 输出图片用于ajax登录
      header("Content-Type: image/png");
      readfile("empty.png");
    }      
               
      /*
         * 开启session并且防止session劫持
   */

    protected function _session_start()
    {
            //如果session已经开水器false
            
       if ($this->started) return;
      $this->started = true;
      // 应用session
      $matches = null;
               
                $cookie = $this->input->cookie(session_name());
               
                //如果通过request方式获取到PHPSSID 并且匹配本规则
      if (isset($cookie) && preg_match('/^SSO-(\w*+)-(\w*+)-(*+)$/', $cookie, $matches)) {
               
                $sid = $cookie;
                        
                  if (isset($this->links_path) && file_exists("{$this->links_path}/$sid")) {
                            session_id(file_get_contents("{$this->links_path}/$sid"));
                            session_start();
                            setcookie(session_name(), "", 1);
                  } else {
                              session_start();
                  }

            if (!isset($_SESSION['client_addr'])) {
                session_destroy();
                $this->fail("Not attached");
            }

            if ($this->generateSessionId($matches, $matches, $_SESSION['client_addr']) != $sid) {
                session_destroy();
                $this->fail("Invalid session id");
            }

            $this->broker = $matches;
            return;
      }

      // 开启用户会话
      session_start();
                //如果存在客户端IP并且客户端IP和服务端不一致,更新SESSIONID
      if (isset($_SESSION['client_addr']) && $_SESSION['client_addr'] != $_SERVER['REMOTE_ADDR']) session_regenerate_id(true);
                //如果存在客户端IP并且一致,客户端IP设置为服务端IP
      if (!isset($_SESSION['client_addr'])) $_SESSION['client_addr'] = $_SERVER['REMOTE_ADDR'];
    }      

    /**
         * 通过session token生成session id
   *
   * @return string
   */
    protected function generateSessionId($broker, $token, $client_addr=null)
    {
                //验证broker
                $info = $this->broker_model->get_broker_by_broker($broker);      
                if ($info) {
                        $secret = $info['secret'];
                }else{
                        return null;
                }
                //如果客户端地址没有设置,获取客户端IP
      if (!isset($client_addr)) $client_addr = $_SERVER['REMOTE_ADDR'];
                //根据 参数生出客户端session文件名称
      return "SSO-{$broker}-{$token}-" . md5('session' . $token . $client_addr . $secret);
    }
      
    /**
         * 通过session token生成session id
   *
   * @return string
   */
    protected function generateAttachChecksum($broker, $token)
    {
            
                //验证broker
                $info = $this->broker_model->get_broker_by_broker($broker);      
                if ($info) {
                        $secret = $info['secret'];
                }else{
                        return null;
                }
               
      return md5('attach' . $token . $_SERVER['REMOTE_ADDR'] . $secret);
    }
      
    /**
   * 错误
   *
   * @param string $message
   */
    protected function fail($message)
    {
      header("HTTP/1.1 406 Not Acceptable");
      echo $message;
      exit;
    }
      
    /**
   * 登录失败
   *
   * @param string $message
   */
    protected function failLogin($message)
    {
      header("HTTP/1.1 401 Unauthorized");
      echo $message;
      exit;
    }
      
}

/* End of file sso.php */
/* Location: ./application/controllers/sso.php */


代理端:

<?php
/**
* 单点登录服务
*/
class Sso
{
      /**
         * 取消服务端 HTTP401
         */
      public $pass401=false;

    /**
   * SSO服务地址
   * @var string
   */
    public $url = "http://sso.lab.mycihi.cn/sso/";

    /**
   * 代理ID
   * @var string
   */
    public $broker = "cihi";

    /**
   * 秘药
   * @var string
   */
    public $secret = "1A9624F80F6FCE52325B74255ABE7A94";

    /**
   * 不能比服务端设置的小
   * @var string
   */
    public $sessionExpire = 1800;

    /**
   * SESSION hash
   * @var string
   */
    protected $sessionToken;

    /**
   * 用户信息
   * @var array
   */
    protected $userinfo;
      
      
      /**
         * 错误信息
         *
         * 1. no_username 缺少用户名
         * 2. no_password 缺少密码
         * 3. user_not_exists 用户不存在
         * 4. bad_password 密码错误
         * 4. unknown未知
         *
         */
      public$error;


    /**
   * 构造函数
   */
    public function __construct($auto_attach=true)
    {
            $this->test_connectiong();
            //如果cookie存在session_token
      if (isset($_COOKIE['session_token'])) $this->sessionToken = $_COOKIE['session_token'];
      //如果设置自动粘贴token并且不存在sessiontoken,带上参数跳转的服务端
      if ($auto_attach && !isset($this->sessionToken)) {
                //跳转至SSO
            header("Location: " . $this->getAttachUrl() . "&redirect=". urlencode("http://{$_SERVER["SERVER_NAME"]}{$_SERVER["REQUEST_URI"]}"), true, 307);
            exit;
      }
    }

      //测试通讯

      public function test_connectiong ()
      {
                if(isset($_GET['testsso'])&&$_GET['testsso']==1){
                        echo "connected";
                }
      }

    /**
   * 获取客户端的session_token
   *
   * @return string
   */
    public function getSessionToken()
    {
            //如果没有生成过session_token 生成session_token
      if (!isset($this->sessionToken)) {
                //随机申城session_token
            $this->sessionToken = md5(uniqid(rand(), true));
                        //吧session_token写入cookie
            setcookie('session_token', $this->sessionToken, time() + $this->sessionExpire);
      }

      return $this->sessionToken;
    }

    /**
   * 生成session id
   *
   * @return string
   */
    protected function getSessionId()
    {
                if (!isset($this->sessionToken)) return null;
      return "SSO-{$this->broker}-{$this->sessionToken}-" . md5('session' . $this->sessionToken . $_SERVER['REMOTE_ADDR'] . $this->secret);
    }

    /**
         * 获取URL并传递session到sso服务器
   *
   * @return string
   */
    public function getAttachUrl()
    {
                $token = $this->getSessionToken();
                //根据token和IP和代理端秘药生成校验码传递给服务端
                $checksum = md5("attach{$token}{$_SERVER['REMOTE_ADDR']}{$this->secret}");
                //拼接URL 传递 sessioin_token和校验码到服务端
      return "{$this->url}attach?broker={$this->broker}&token=$token&checksum=$checksum";
    }   


    /**
   * WEB登录
   *
   * @param string $username
   * @param string $password
   * @return boolean
         *
   */
    public function login($username, $password)
    {   
      list($ret, $body) = $this->serverCmd('login', array('username'=>$username, 'password'=>$password));

      switch ($ret) {
               
            case 200: $this->parseInfo($body);
                      return 1;
            case 401: $this->error= $body;
                                          return 0;
            default:$this->error= $body;
                                          return 0;

      }
    }
      
    /**
   * PC登录
   *
   * @param string $username
   * @param string $password
   * @return boolean
         *
   */
    public function pc_login($cihiuserno, $cihikey)
    {   
      list($ret, $body) = $this->serverCmd('pc_login', array('cihiuserno'=>$cihiuserno, 'cihikey'=>$cihikey));

      switch ($ret) {
               
            case 200: $this->parseInfo($body);
                      return 1;
            case 401: $this->error= $body;
                                          return 0;
            default:$this->error= $body;
                                          return 0;

      }
    }

      
                        

    /**
   * 退出单点登录
   */
    public function logout()
    {
                if(isset($_GET['testsso'])&&$_GET['testsso']==1){
                        echo "connected";exit();
                }
            //header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
               
      list($ret, $body) = $this->serverCmd('logout');

                echo $body;

                setcookie('session_token', '');
               
               
               
               


    }



    /**
   * 获取SSO当前登陆用户信息
   */
    public function getInfo()
    {
      if (!isset($this->userinfo)) {
               
            list($ret, $body) = $this->serverCmd('info');
            switch ($ret) {
                     case 200:
                              return $this->parseInfo($body);
                     case 401: $this->error= $body;
                                                return 0;
                     default:$this->error= $body;
                                                return 0;
            }
      }

      return $this->userinfo;
    }

    /**
   * 执行CURL请求
   *
   * @param string $cmd   Command
   * @param array$varsPost variables
   * @return array
         *
   */
    protected function serverCmd($cmd, $vars = array())
    {
            
      $curl = curl_init($this->url . '/' . urlencode($cmd));
      curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($curl, CURLOPT_COOKIE, "PHPSESSID=" . $this->getSessionId());
      
      if (!empty($vars)) {
            curl_setopt($curl, CURLOPT_POST, true);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $vars);
       }

                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

      $body = curl_exec($curl);

               
      $ret = curl_getinfo($curl, CURLINFO_HTTP_CODE);
      
      if (curl_errno($curl) != 0) throw new Exception("SSO failure: HTTP request to server failed. " . curl_error($curl));

      return array($ret, $body);
    }
      

    /**
   * 解析返回用户信息数据
   *
   * @param string $json
   */
    protected function parseInfo($json)
    {
                $josn = json_decode($json);
      return $this->userinfo = (array)$josn;
    }
      
      /**
         * 获取错误信息
         */
      
      public function get_error()
      {
                return $this->error;
      }      
}










715241005 发表于 2017-10-23 17:59:38

楼主可以发一份全套代码吗?3Q715241005@qq.com

adamYm 发表于 2016-1-4 17:35:38

楼主可以发一份全套代码吗?3Q617003229@qq.com

zlq 发表于 2019-6-18 20:37:02

你好,可以提供一下完整代码吗?谢谢,1563185851@qq.com

kajmlqy 发表于 2013-10-21 15:52:37

赞一个 谢谢分享

liujihaozhy 发表于 2013-10-29 23:36:56

{:1_1:}

shan199 发表于 2013-11-9 23:13:45

这个有用,收藏了

martinwangjun 发表于 2014-3-19 09:26:30

记号,收藏一下。

halloheihei 发表于 2014-3-27 15:06:43

谢谢

い微风ァ飘叶 发表于 2014-3-27 16:29:20

不错的思路,mark一记,以备后用:lol

一叶扁舟 发表于 2014-4-24 15:33:16

:lol学习了备用~~

Aloghli 发表于 2014-9-24 19:50:04

{:1_1:}

cheman 发表于 2015-3-12 10:12:25

能给一份代码我参考么?楼主
页: [1] 2 3
查看完整版本: PHP实现跨域单点登录(CI框架)