完全跨站点跨域名单点(SSO)同步登录和注销
先来说说什么是单点登录(SSO)。来自百科的介绍:SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
首先想到的单点登录, 应该就是, 在某个服务器或者站点进行登录, 登录之后携带登录ticket, 跳转到原来登录的站点。原来登录的站点通过远程验证ticket是否合法。这个方法很容易, 只要所有连接都带上ticket参数, 就可以不用登录了。
这样单点登录会有一个局限性, 比如下例场景(如天猫和淘宝):www.a.cn和www.b.net两个站点都在浏览器中打开了, 两个站点都未登录。然后a站点登录, 再切换到b站点的窗口刷新浏览器, 可以发现b站点依然没有登录。因为b站点没有ticket。
如何实现呢? 下面有我自己(Jinko)的个人想法:a站点登录后, 将ticket存到cookie中, 此时a站点是已经登录的。但是b站点无法读取a站点的cookie。因为b站点和a站点是不同根域下的, 这个时候我们应该想到跨域。想到跨域就会想到ajax, canvas多么蛋疼(canvas在将非当前域的img绘制到画布中时进行toDataURL时会有安全限制),他们都会因为跨域安全问题而限制了(当然还有iframe :))。 解决方案就是jsonp, 每当想到跨域我就想到jsonp。没错, 它确实为跨域而生(要不然也没人用它)。
好, 有了jsonp以上问题就迎刃而解, 思路变成如下:
a站点登录后, b站点通过jsonp获取a站点的ticket, 写到当前站点(b)的cookie里,并执行登录动作,更新页面数据。这时候, 后续的所有页面都可以通过cookie里的ticket进行远程验证。而且从a站点到b站点都无需再url中带上ticket(除了注销登录)。真是方便多了。a站点注销登录就直接从cookie取ticket并将对于ticket对应的数据删去(一般存于数据库,或者redis、memcache缓存里面)。在下面的例子里, 为方便我是存于文件 O(∩_∩)O。
接下来就是代码例子, 我用的是php来实现, 代码结构如下
index.php为站点首页, login.php为登录页面, session.lib.php 是自己实现的session存储机制, session-api.php是统一登录接口,.sessioncache目录存的是session缓存文件。看文件目录结构相当简单
要注意的是, 请不要直接用localhost去访问, 要新建两个虚拟主机分别用域名www.a.cn和www.b.net。请修改host文件使它们指向127.0.0.1
以下是代码:
www.a.cn/index.php:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * by Jinko Wu 5 * Date: 2015/12/17 6 */ 7 //允许IE等浏览器跨域访问cookie 8 header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); 9 error_reporting(E_ALL ^ E_NOTICE); 10 require "session.lib.php"; 11 $session = jsession_start(); 12 13 echo '<meta charset="utf-8"/>'; 14 if(isset($session)) { 15 echo 'A 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.jsession_id().'">退出</a>'; 16 } else { 17 echo 'A 您还没有登录!'.'<a href="http://www.a.cn/login.php">去登录</a>'; 18 }
www.a.cn/login.php:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * by Jinko Wu 5 * Date: 2015/12/17 6 */ 7 //允许IE等浏览器跨域访问cookie 8 header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); 9 ?> 10 <meta charset="utf-8"/> 11 <form action="session-api.php?action=login" method="post"> 12 <input type="text" name="name"> 13 <input type="hidden" name="redirect" value="<?php echo $_SERVER['HTTP_REFERER'] ?>"> 14 <input type="submit"> 15 </form>
www.a.cn/session.lib.php:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * by Jinko Wu 5 * Date: 2015/12/17 6 */ 7 function jsession_start() 8 { 9 if(!$_COOKIE['__SESSID']) { 10 jsession_regenerate_id(); 11 } 12 13 return jsession_update(); 14 } 15 16 function jsession_update($session_id=null) 17 { 18 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; 19 20 if($session_id == '') { 21 return null; 22 } 23 24 if($session_id && $data = jsession_is_valid($session_id)) { 25 jsession_save($data); 26 setcookie('__SESSID', $session_id, time()+jsession_live_time()); 27 return $data; 28 } 29 30 return null; 31 } 32 33 function jsession_regenerate_id() 34 { 35 $sessid = jsession_generate_id(); 36 37 if(jsession_id() != '' && file_exists('.sessioncache/' .jsession_id())) { 38 rename('.sessioncache/' .jsession_id(), '.sessioncache/' .$sessid); 39 } 40 41 $_COOKIE['__SESSID'] = $sessid; 42 setcookie('__SESSID', $sessid, time()+jsession_live_time()); 43 } 44 45 function jsession_generate_id() 46 { 47 return 's'.base_convert(rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999), 10, 36); 48 } 49 50 function jsession_id() 51 { 52 return $_COOKIE['__SESSID']; 53 } 54 55 function jsession_live_time() 56 { 57 $gc_maxlifetime = ini_get('session.gc_maxlifetime'); 58 $gc_maxlifetime = $gc_maxlifetime == '' ? 1440 : $gc_maxlifetime; 59 return $gc_maxlifetime; 60 } 61 62 function jsession_is_valid($session_id) 63 { 64 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; 65 66 if($session_id == '') { 67 return false; 68 } 69 70 if(file_exists('.sessioncache/' .$session_id)) { 71 $data = unserialize(@file_get_contents('.sessioncache/' . $session_id)); 72 return time() <= $data['time'] ? $data['data'] : false; 73 } else { 74 return false; 75 } 76 } 77 78 function jsession_data($session_id=null) 79 { 80 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; 81 82 if($session_id == '') { 83 return null; 84 } 85 86 if($data = jsession_is_valid($session_id)) { 87 return $data; 88 } 89 90 return null; 91 } 92 93 function jsession_save($data) 94 { 95 96 if(!is_dir('.sessioncache')) { 97 mkdir('.sessioncache', 0777); 98 } 99 100 if($_COOKIE['__SESSID'] == '') { 101 return null; 102 } 103 104 $file = '.sessioncache/'.$_COOKIE['__SESSID']; 105 $fp = fopen($file , 'w'); 106 107 if(flock($fp , LOCK_EX)) { 108 fwrite($fp, serialize(array('time' => time() + jsession_live_time(), 'data' => $data))); 109 flock($fp, LOCK_UN); 110 } 111 112 fclose($fp); 113 return $data; 114 } 115 116 function jsession_destory($session_id) 117 { 118 if($session_id == '') { 119 return ; 120 } 121 122 if($session_id == $_COOKIE['__SESSID']) { 123 setcookie('__SESSID', ''); 124 $_COOKIE['__SESSID'] = null; 125 } 126 127 if(file_exists('.sessioncache/' .$session_id)) { 128 @unlink('.sessioncache/' .$session_id); 129 } 130 }
www.a.cn/session-api.php:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * by Jinko Wu 5 * Date: 2015/12/17 6 */ 7 //允许IE等浏览器跨域访问cookie 8 header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); 9 error_reporting(E_ALL ^ E_NOTICE); 10 require 'session.lib.php'; 11 12 if($_REQUEST['action'] == 'check') { 13 $session = jsession_is_valid($_REQUEST['id']); 14 echo json_encode(array('session' => $session)); 15 16 } else if($_REQUEST['action'] == 'logout') { 17 if($_REQUEST['sessid'] !== null) { 18 jsession_destory($_REQUEST['sessid']); 19 } 20 21 echo '<meta charset="utf-8"/>'; 22 echo '退出登录成功, 正在跳转...'; 23 $_SERVER['HTTP_REFERER'] = $_SERVER['HTTP_REFERER'] == '' ? '/' : $_SERVER['HTTP_REFERER']; 24 echo '<script type="text/javascript">window.location.href = "' . $_SERVER['HTTP_REFERER'] . '";</script>'; 25 26 } else if($_REQUEST['action'] == 'login') { 27 jsession_start(); 28 $data = jsession_save(array('name' => trim($_REQUEST['name']))); 29 $redirect = $_REQUEST['redirect'] ? $_REQUEST['redirect'] : 'http://www.a.cn'; 30 echo '<meta charset="UTF-8"><script type="text/javascript">window.location.href = "'.$redirect.'";</script>'; 31 32 } else { 33 $session = jsession_start(); 34 35 if($session && trim($_REQUEST['call']) != '' && jsession_id() != '') { 36 echo $_REQUEST['call'] . '('.json_encode(array('sessid' => jsession_id(), 'session' => $session)).')'; 37 } 38 }
www.b.net/index.php:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * by Jinko Wu 5 * Date: 2015/12/17 6 */ 7 ?> 8 <meta charset="utf-8"/> 9 <script type="text/javascript"> 10 function setCookie(name,value) 11 { 12 var Days = 30; 13 var exp = new Date(); 14 exp.setTime(exp.getTime() + Days*24*60*60*1000); 15 document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString(); 16 } 17 18 //jsonp 登录函数 19 function jsonp_do_login(data) 20 { 21 document.getElementById('name').innerHTML = 'B 您好:' + data.session.name + '<a href="http://www.a.cn/session-api.php?action=logout&sessid='+data.sessid+'">退出</a>'; 22 console.log(data); 23 setCookie('__SESSID', data.sessid); 24 } 25 </script> 26 <?php 27 error_reporting(E_ALL ^ E_NOTICE); 28 session_start(); 29 $session = check_session(); 30 $sessid = $_COOKIE['__SESSID']; 31 32 if($session) { 33 echo 'B 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.$sessid.'">退出</a>'; 34 } else { 35 echo '<span id="name">B 您还没有登录!<a href="http://www.a.cn/login.php">去登录</a></span>'; 36 } 37 38 function check_session() 39 { 40 $sessid = $_COOKIE['__SESSID']; 41 $json = file_get_contents("http://www.a.cn/session-api.php?id=$sessid&action=check"); 42 $json_data = json_decode($json, true); 43 44 if($json_data == null || empty($json_data['session'])) { 45 return false; 46 } else { 47 return $json_data['session']; 48 } 49 } 50 51 ?> 52 53 <?php if(!$session): ?> 54 <script type="text/javascript" src="http://www.a.cn/session-api.php?call=jsonp_do_login&<?php echo rand()?>"></script> 55 <?php endif; ?>
点击这里下载打包好的代码: https://files.cnblogs.com/files/JinkoWu/MultiSiteSingleLogin.zip
最后附上一张示例图片: