PHP单点登录的简单实现以及webserver的简单使用

PHP单点登录的简单实现以及webserver的简单使用

简单实现三个站点的单点登录,在一个站点登录,其他站点自动登录,一个站点退出,其他站点同时退出

说明:功能的实现基于cookie,如果浏览器关闭了cookie的功能,将无法实现多点登陆。

假设有三个站点

  • siteA 域名为sitea.xxx
  • siteB 域名为siteb.xxx
  • siteC 域名为sitec.xxx

siteC提供统一登录认证服务

要实现单点登录要满足如下的条件:

  1. 站点SESSION的共享,不管用户数据库是否是单独的,SESSION一定要是公共的。(这里用memcached解决共享SESSION的问题)
  2. 所有站点能共享一个登录凭证。(对php程序来说 ,SESSION ID 是最简单的方法了)

所以,只要能把siteA的session id 传递给 siteB等其他站点就好办了,我们可以使用jsonp或者隐藏的iframe达到这个目的

首先在sitec上建立webserver服务端,(其他方式的登录认证API接口都可以)
  • soap_server.php

第一步,开启session,保存到memcache;

第二步,建立webserver,建议使用PHP自带的soap扩展.默认soap的扩展是没有加载的,所以你可能需要检查php.ini是否打开了

soap扩展我们主要使用三个类:soapSever,soapCilent和soapFault;

  1. SoapServer用于创建php服务器端页面时定义可被调用的函数,方法,对象,类及返回响应数据。
  2. SoapClient用于调用远程服务器上的SoapServer页面,并实现了对相应函数的调用。
  3. SoapFault用于生成soap访问过程中可能出现的错误。

Soap服务有两种模式:

  1. WSDL模式:需要手动创建一个WSDL文件.如果各个站点是不同的语言写的,比如有的用java,有的用PHP,那么用wsdl吧,接口也一目了然 
  2. Non-WSDL模式:如果所有站点都是PHP的,这种方法简单省事,效率也要高一点。

这里使用Non-WSDL的模式

ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', '127.0.0.1:11211');
session_start();
//开启webserver,启动登录和认证服务
require_once 'Authorize.class.php';
$options = array(
    'location' => 'http://sitec.xxx/soap_server.php',
    'uri' => 'soap_server.php'
);
//无wsdl模式
$wsdl = null; 
$soap = new SoapServer($wsdl, $options);
$soap->setClass("Authorize");
$soap->handle();
  • Authorize.class.php

你的webserver类

class Authorize {
    const KEY = "a*v%^*(OH"; //ticket加密解密的密钥

    /**
     * login  登录服务并返回多点登录的ticket
     * @param type $username 用户登录名或者id
     * @param type $password    登录密码
     * @param type $login_site 来源的站点(为防止加密解密失败,约定为 xxxx.xx(如:abc.xxx) 格式,自行处理)
     * @param type $ip  登录用户客户端IP
     * @return array  返回数组,error=>错误信息,'sessid'=>session id,'sso_script'=>多点登录接口脚本,ticket=>多点登录凭证
     */

    public function login($username = '', $password = '', $login_site = '', $ip = '') {      
        if ($login_site == '' || $username == '' || $password == '') {
            return array();
        }
        //分配并获取session_id();
        $sid = session_id();
        //返回值结构
        $result = array(
            'error'=>'',  //错误信息
            'sessid' => $sid, //session id
            "sso_script" => 'http://sitec.xxx/sso.php?ticket=', //多点登录的脚本地址
            'ticket'=>''
        );

        //连接数据库,可能根据登录站点不同,连接不同的数据库
        $file_path = './data.xml';
        if (!file_exists($file_path)) {
            $result['error'] = 'can not connect db';
            return $result;
        }
        $f = file_get_contents($file_path);
        $xml = new SimpleXMLElement($f);
        $users = $xml->xpath("/users/user[name='{$username}']");
        $user = $users[0];
        if ($user) {
            $uname = (string) $user->name;
            $upassword = (string) $user->password;
            if ($password != $upassword) {
                $result['error'] =  'username or password is wrong !';
                return $result;
            }
            $uid = (string) $user->attributes()->id;
            $_SESSION['uid'] = $uid;
            $_SESSION['uname'] = $uname;
            $_SESSION['upassword'] = $upassword;
            $_SESSION['islogin'] = 'true';
            $_SESSION['login_site'] = $login_site;
            $_SESSION['ip'] = $ip;
            $_SESSION['ticket_expire'] = 1;  //ticket登录计数器,获取需要登录的站点个数,每登录一个-1;<=0将不能使用
            //生成ticket
            require_once 'encrypt.php';
            $key = $ip . self::KEY . $login_site; //用ip和登录地址做密钥防止接口连接被复制粘贴;如果session周期太长,仍然是不安全的
            $ticket = encrypt($result['sessid'], "E", $key);
            $result['ticket'] = $ticket;
            $result['sso_srcipt'] .= $ticket;
            return $result;
        }
    }
    /**
     * decryptTicket ticket解密并验证有效性
     * @param type $ticket
     * @param type $server_name
     * @return type array("sid")
     */
    public function decryptTicket($ticket = '', $login_site = '', $ip = '') {
        //返回值结构
        $res = array(
            "sid" => '',
            "error" => ''
        ); 
        if ($ticket == '' || $login_site == '') {
            $res['error'] = "tciket和login_site不能为空";
            return $res;
        }
        //解密ticket,获得session id后返回
        $key = $ip . self::KEY . $login_site;
        require_once 'encrypt.php';
        $sid = encrypt($ticket, "D", $key);
        if($sid){
           $res['sid'] = $sid;
        }
        return $res;
    }

    /**
     * checkTickerExpire 检查ticket是否有效
     * @return int 1为认证成功,0为失败
     */
    public function checkTickerExpire() {
        if ($_SESSION['ticket_expire'] && ($_SESSION['ticket_expire'] -- > 0)) {
            return 1;
        }
        return 0;
    }

}
  • encrypt.php

任意加密解密字符串的函数或者类库都可以

  • sso.php

sso_script,这个是在其他各个站点登录成功之后加载的脚本。

<?php
/*
 * 远程登录jsonp脚本,这个脚本放在每个网站需要登录的页面,也可以在登录成功时用js动态加载
 * 各个站点从配置获取
 * 确保能获得用户IP和当前登录的站点域名,两者可能会被用来作为钥匙解密ticket
 */
//ini_set('session.save_handler', 'memcache');
//ini_set('session.save_path', '127.0.0.1:11211');
//session_start();
//如果要在siteC上也登录,就传递过来ticket进行认证或者把siteC也加入到到sites数组中,

header("Content-type:text/javascript;charset=utf-8");

$sites = array('http://sitea.xxx', 'http://siteb.xxx');
$refer_info = pathinfo($_SERVER["HTTP_REFERER"]);
$refer = $refer_info['dirname'];

$ticket = '';
if (is_string($_GET['ticket'])) {
    $ticket = $_GET['ticket'];
}

//irame框架
$iframes = '';
foreach ($sites as $site) {
    if($refer == $site){
        continue;
    }
//    $iframes .= '<iframe style="display:none" width=0 height=0 frameborder=0  src="' . $api . '/login_api.php"></iframe>';
    $iframes .= '<iframe   src="' . $site . '/login_api.php"></iframe>';
}

$js = <<<JSCODE
            window.onload = function(){
                var ticket = "{$ticket}"
                if(!ticket){
                    //从cookie从获取ticket
                    var ticket_arr = document.cookie.split(";").filter(
                        function(e){
                            var cookie = e.split("=")
                            return cookie[0].replace(" ","") === "ticket"
                         }
                    )
                    if(ticket_arr[0]){
                        ticket = ticket_arr[0].split("=")[1]
                    }else{
                        //ticket不存在
                        return
                    }   
                }

                //创建隐藏的ifrme,调用认证接口
                div = document.createElement("div")
                div.innerHTML = '{$iframes}'               
                var iframes = div.childNodes
                var len = iframes.length
                for (var i= 0;i<len;i++){
                    new_src = iframes[i].getAttribute("src")+'?ticket='+ticket
                    iframes[i].setAttribute('src',new_src)
                }
                document.body.appendChild(div)
                //销毁ticket,防止反复登录
                var date = new Date();
                date.setTime(date.getTime() - 10000);
                document.cookie = "ticket" + "=a; expires=" + date.toGMTString();
            }
JSCODE;
echo $js;
站点的登录认证,session的同步
  • index.php webserver登录认证

这里仅仅是演示soapColent的使用,就像调用本地对象方法一样。

function do_login() {
    $username = is_string($_REQUEST['username']) ? $_REQUEST['username'] : '';
    $password = is_string($_REQUEST['password']) ? $_REQUEST['password'] : '';

    if ($username == '' || $password == '') {
        echo 'bad username or password !';
        return;
    }
    //确保这里给的site_name要和sso_script提供的login_site一致,否则会导致认证失败
    $site_name = $_SERVER['SERVER_NAME'];
    $ip = $_SERVER["REMOTE_ADDR"];
    //开启sopa客户端,无wsdl模式
    $wsdl = null;
    $options = array(
        'location' => 'http://sitec.xxx/soap_server.php',
        'uri' => 'soap_server.php'
    );
    $soap = new SoapClient($wsdl, $options);
    $result = $soap->login($username, $password, $site_name, $ip);
    if ($result['error']) {
        echo $result['error'];
    } else {
        setcookie('PHPSESSID', $result['sessid']);
        //如果是跳转到某个页面,将ticket保存到cookie,手动添加sso_script脚本
        //如果是ajax可以直接返回 $result['sso_sript'];登录成功后加载这个jsonp即可
        setcookie('ticket', $result['ticket']);
        header('location:index.php?act=show_index');
    }
}

//销毁session或者标识用户状态,达到单点退出的目的
function do_logout() {
    //session_unset(); 
    //session_destroy();
    $_SESSION['islogin'] = 'false';
    header('location:index.php?act=show_login');
}
  • 手动在HTML页面加上单点登录的js本,或者使用jsonp请求ajax登录成功后返回的脚本连接
function show_index() {
    if (empty($_SESSION['islogin']) || $_SESSION['islogin'] == 'false') {
        header('location:index.php?act=show_login');
    }
    echo '<html><head><script type="text/javascript" src="http://sitec.xxx/sso.php"></script></head><body>';
    $sid = session_id();
    echo "welcome {$_SESSION['uname']},your id is {$_SESSION['uid']},password is {$_SESSION['upassword']},sessionid is {$sid}";
    echo " ,ip is {$_SESSION['ip']} , from {$_SESSION['login_site']}";
    echo '<a href="?act=do_logout">[logout]</a>';

    echo '</body></html>';
}
  • login_api.php 在每个站点的目录下配置一个登入脚本

这个脚本接收传递过来的ticket,然后连接websever认证,获取session_id ;将session_id 写入cookie

//我也不知道这是什么,不过没有这个头,IE下,你的irame将无法写入cookie,其他浏览器没有这个问题
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', '127.0.0.1:11211');
session_start();
$site = $_SERVER['SERVER_NAME'];
if ($_SESSION['islogin'] === 'true') {
    echo $site, '重复登录,直接返回';
    exit(0);
}
if ($_GET['ticket'] && $_SERVER['SERVER_NAME']) {
    $ip = $_SERVER["REMOTE_ADDR"];
    $refer_info = parse_url($_SERVER["HTTP_REFERER"]);
    $login_site = $refer_info['host'];
    $ticket = $_GET['ticket'];
    //认证ticket
    $soap = new SoapClient(null, array("location" => "http://sitec.xxx/soap_server.php", "uri" => "soap_server.php"));
    $res = $soap->decryptTicket($ticket, $login_site, $ip);
    if (!$res['error'] && $res['sid']) {
        $sid = $res['sid'];
        $soap->__setCookie("PHPSESSID", $sid);
        if ($soap->checkTickerExpire()) {
            setcookie("PHPSESSID", $sid);
            echo $site, '登录成功';
        }
    } else {
        echo $site, "登录失败", $res['error'];
    }
}

总结

  • 实现方法主要是通过iframe或者跨域把session id写入cookie
  • 方法的实现依赖与cookie的跨域写入和session的多站共享
  • 优点是js后台执行,不影响登录速度
  • 缺点是站点多了的话iframe请求的网站会比较多,影响网页的加载速度。

  • 下载代码http://url点cn/RFbTBK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值