先来说说什么是单点登录(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:

 <?php
/**
* Created by PhpStorm.
* by Jinko Wu
* Date: 2015/12/17
*/
//允许IE等浏览器跨域访问cookie
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
error_reporting(E_ALL ^ E_NOTICE);
require "session.lib.php";
$session = jsession_start(); echo '<meta charset="utf-8"/>';
if(isset($session)) {
echo 'A 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.jsession_id().'">退出</a>';
} else {
echo 'A 您还没有登录!'.'<a href="http://www.a.cn/login.php">去登录</a>';
}

www.a.cn/login.php:

 <?php
/**
* Created by PhpStorm.
* by Jinko Wu
* Date: 2015/12/17
*/
//允许IE等浏览器跨域访问cookie
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
?>
<meta charset="utf-8"/>
<form action="session-api.php?action=login" method="post">
<input type="text" name="name">
<input type="hidden" name="redirect" value="<?php echo $_SERVER['HTTP_REFERER'] ?>">
<input type="submit">
</form>

www.a.cn/session.lib.php:

 <?php
/**
* Created by PhpStorm.
* by Jinko Wu
* Date: 2015/12/17
*/
function jsession_start()
{
if(!$_COOKIE['__SESSID']) {
jsession_regenerate_id();
} return jsession_update();
} function jsession_update($session_id=null)
{
$session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; if($session_id == '') {
return null;
} if($session_id && $data = jsession_is_valid($session_id)) {
jsession_save($data);
setcookie('__SESSID', $session_id, time()+jsession_live_time());
return $data;
} return null;
} function jsession_regenerate_id()
{
$sessid = jsession_generate_id(); if(jsession_id() != '' && file_exists('.sessioncache/' .jsession_id())) {
rename('.sessioncache/' .jsession_id(), '.sessioncache/' .$sessid);
} $_COOKIE['__SESSID'] = $sessid;
setcookie('__SESSID', $sessid, time()+jsession_live_time());
} function jsession_generate_id()
{
return 's'.base_convert(rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999), 10, 36);
} function jsession_id()
{
return $_COOKIE['__SESSID'];
} function jsession_live_time()
{
$gc_maxlifetime = ini_get('session.gc_maxlifetime');
$gc_maxlifetime = $gc_maxlifetime == '' ? 1440 : $gc_maxlifetime;
return $gc_maxlifetime;
} function jsession_is_valid($session_id)
{
$session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; if($session_id == '') {
return false;
} if(file_exists('.sessioncache/' .$session_id)) {
$data = unserialize(@file_get_contents('.sessioncache/' . $session_id));
return time() <= $data['time'] ? $data['data'] : false;
} else {
return false;
}
} function jsession_data($session_id=null)
{
$session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id; if($session_id == '') {
return null;
} if($data = jsession_is_valid($session_id)) {
return $data;
} return null;
} function jsession_save($data)
{ if(!is_dir('.sessioncache')) {
mkdir('.sessioncache', 0777);
} if($_COOKIE['__SESSID'] == '') {
return null;
} $file = '.sessioncache/'.$_COOKIE['__SESSID'];
$fp = fopen($file , 'w'); if(flock($fp , LOCK_EX)) {
fwrite($fp, serialize(array('time' => time() + jsession_live_time(), 'data' => $data)));
flock($fp, LOCK_UN);
} fclose($fp);
return $data;
} function jsession_destory($session_id)
{
if($session_id == '') {
return ;
} if($session_id == $_COOKIE['__SESSID']) {
setcookie('__SESSID', '');
$_COOKIE['__SESSID'] = null;
} if(file_exists('.sessioncache/' .$session_id)) {
@unlink('.sessioncache/' .$session_id);
}
}

www.a.cn/session-api.php:

 <?php
/**
* Created by PhpStorm.
* by Jinko Wu
* Date: 2015/12/17
*/
//允许IE等浏览器跨域访问cookie
header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
error_reporting(E_ALL ^ E_NOTICE);
require 'session.lib.php'; if($_REQUEST['action'] == 'check') {
$session = jsession_is_valid($_REQUEST['id']);
echo json_encode(array('session' => $session)); } else if($_REQUEST['action'] == 'logout') {
if($_REQUEST['sessid'] !== null) {
jsession_destory($_REQUEST['sessid']);
} echo '<meta charset="utf-8"/>';
echo '退出登录成功, 正在跳转...';
$_SERVER['HTTP_REFERER'] = $_SERVER['HTTP_REFERER'] == '' ? '/' : $_SERVER['HTTP_REFERER'];
echo '<script type="text/javascript">window.location.href = "' . $_SERVER['HTTP_REFERER'] . '";</script>'; } else if($_REQUEST['action'] == 'login') {
jsession_start();
$data = jsession_save(array('name' => trim($_REQUEST['name'])));
$redirect = $_REQUEST['redirect'] ? $_REQUEST['redirect'] : 'http://www.a.cn';
echo '<meta charset="UTF-8"><script type="text/javascript">window.location.href = "'.$redirect.'";</script>'; } else {
$session = jsession_start(); if($session && trim($_REQUEST['call']) != '' && jsession_id() != '') {
echo $_REQUEST['call'] . '('.json_encode(array('sessid' => jsession_id(), 'session' => $session)).')';
}
}

www.b.net/index.php:

 <?php
/**
* Created by PhpStorm.
* by Jinko Wu
* Date: 2015/12/17
*/
?>
<meta charset="utf-8"/>
<script type="text/javascript">
function setCookie(name,value)
{
var Days = 30;
var exp = new Date();
exp.setTime(exp.getTime() + Days*24*60*60*1000);
document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
} //jsonp 登录函数
function jsonp_do_login(data)
{
document.getElementById('name').innerHTML = 'B 您好:' + data.session.name + '<a href="http://www.a.cn/session-api.php?action=logout&sessid='+data.sessid+'">退出</a>';
console.log(data);
setCookie('__SESSID', data.sessid);
}
</script>
<?php
error_reporting(E_ALL ^ E_NOTICE);
session_start();
$session = check_session();
$sessid = $_COOKIE['__SESSID']; if($session) {
echo 'B 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.$sessid.'">退出</a>';
} else {
echo '<span id="name">B 您还没有登录!<a href="http://www.a.cn/login.php">去登录</a></span>';
} function check_session()
{
$sessid = $_COOKIE['__SESSID'];
$json = file_get_contents("http://www.a.cn/session-api.php?id=$sessid&action=check");
$json_data = json_decode($json, true); if($json_data == null || empty($json_data['session'])) {
return false;
} else {
return $json_data['session'];
}
} ?> <?php if(!$session): ?>
<script type="text/javascript" src="http://www.a.cn/session-api.php?call=jsonp_do_login&<?php echo rand()?>"></script>
<?php endif; ?>

点击这里下载打包好的代码: http://files.cnblogs.com/files/JinkoWu/MultiSiteSingleLogin.zip

最后附上一张示例图片:

完全跨站点跨域名单点(SSO)同步登录和注销的更多相关文章

  1. Php开发完全跨站点跨域名单点(SSO)同步登录和注销

    From:http://www.cnblogs.com/JinkoWu/p/5056646.html 先来说说什么是单点登录(SSO).来自百科的介绍:SSO英文全称Single Sign On,单点 ...

  2. 网站跨站点单点登录实现--cookie

    至于什么是单点登录,举个例子,如果你登录了msn messenger,访问hotmail邮件就不用在此登录.一般单点登录都需要有一个独立的登录站点,一般具有独立的域名,专门的进行注册,登录,注销等操作 ...

  3. Yii2 多域名跨域同步登录退出

    在平台开发过程中,项目分为前台(frontend)www.xxx.com和后台(backend) yun.xxx.com两部分,绑定两个域名, 我们知道在没有绑定域名的时候前后台可以同步登录和退出,但 ...

  4. asp.net关于Cookie跨域(域名)的问题

    Cookie是一个伟大的发明,它允许Web开发者保留他们的用户的登录状态.但是当你的站点有一个以上的域名时就会出现问题了.在Cookie规范上 说,一个cookie只能用于一个域名,不能够发给其它的域 ...

  5. .Net 站点跨域问题及解决方法

    一.什么是站点跨域 了解跨域之前, 先了解下什么同源策略?百度百科:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功 ...

  6. Appscan漏洞之跨站点请求伪造(CSRF)

    公司前段时间使用了Fortify扫描项目代码,在修复完这些Fortify漏洞后,最近又启用了Appscan对项目代码进行漏洞扫描,同样也是安排了本人对这些漏洞进行修复.现在,针对修复过的Appscan ...

  7. IIS下设置跨域访问问题--Access-Control-Allow-Origin 站点跨域请求的问题

    背景: 最近 开发中遇到新需求,把公司的OA系统迁移一套到小程序上面去 有些功能的信息是在小程序 查看 但是文件是在pc端上传的 例如:领导在外出办公 使用小程序查看xxxx.pdf文件  这个时候就 ...

  8. 跨站点脚本攻击XSS

    来源:http://www.freebuf.com/articles/web/15188.html 跨站点脚本攻击是一种Web应用程序的攻击,攻击者尝试注入恶意脚本代码到受信任的网站上执行恶意操作.在 ...

  9. 跨站点请求伪造 - SpringBoot配置CSRF过滤器

    1. 跨站点请求伪造   风险:可能会窃取或操纵客户会话和 cookie,它们可能用于模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务.   原因:应用程序使用的认证方法不充分. ...

随机推荐

  1. JavaScript面向对象之类的创建

    JavaScript对象的定义: 在js中函数极为对象,对象分为二种:对象字变量产生的对象连接到Object.prototype:函数对象连接到Function.prototype 方法:当一个函数被 ...

  2. Qt creator自定义编译运行步骤

    一直用Qt creator开发.无它,只是因为linux下C++ IDE选择不多.同时因为我抛弃了MFC,平时写个小工具还得靠Qt,正好一举两用. 用Qt creator开发一般的工程,是不用修改编译 ...

  3. [Regular Expressions] Match the Same String Twice

    Regular Expression Backreferences provide us a method to match a previously captured pattern a secon ...

  4. Microsoft Visual C++ 不支持long long

    Microsoft Visual C++ 不支持long long 在C/C++中,64为整型一直是一种没有确定规范的数据类型.现今主流的编译器中,对64为整型的支持也是标准不一,形态各异.一般来说, ...

  5. JAVA按字节读取文件

    JAVA的IO流一直都是我比较头疼的部分(我没有系统学过JAVA,一般需要实现什么功能再去看文档). 最近遇到一个需求:一个字节一个字节地读取一个文件.网上很多方法,代码一大堆.我在这里和大家分享一个 ...

  6. Spark IDEA开发环境构建

    本文档基于IEDA构建spark maven应用. date: 2016/8/1 author: wangxl 1.下载IDEA https://www.jetbrains.com/idea/ 2.安 ...

  7. POJ 2195 Going Home / HDU 1533(最小费用最大流模板)

    题目大意: 有一个最大是100 * 100 的网格图,上面有 s 个 房子和人,人每移动一个格子花费1的代价,求最小代价让所有的人都进入一个房子.每个房子只能进入一个人. 算法讨论: 注意是KM 和 ...

  8. 编写简单的 NT 式驱动程序的加载与卸载工具

    写驱动的加载需要用到五个函数: OpenSCManager() CreateService() OpenService() StartService() CloseServiceHandle() 这五 ...

  9. Qt调用外部程序QProcess通信

    mainwindow.cpp文件: -------------------------------- #include "mainwindow.h" #include " ...

  10. KMP与扩展KMP

    原文转自:http://www.cppblog.com/MatoNo1/archive/2011/04/17/144390.aspx KMP:给出两个字符串A(称为模板串)和B(称为子串),长度分别为 ...