完全跨站点跨域名单点(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:
<?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)同步登录和注销的更多相关文章
- Php开发完全跨站点跨域名单点(SSO)同步登录和注销
From:http://www.cnblogs.com/JinkoWu/p/5056646.html 先来说说什么是单点登录(SSO).来自百科的介绍:SSO英文全称Single Sign On,单点 ...
- 网站跨站点单点登录实现--cookie
至于什么是单点登录,举个例子,如果你登录了msn messenger,访问hotmail邮件就不用在此登录.一般单点登录都需要有一个独立的登录站点,一般具有独立的域名,专门的进行注册,登录,注销等操作 ...
- Yii2 多域名跨域同步登录退出
在平台开发过程中,项目分为前台(frontend)www.xxx.com和后台(backend) yun.xxx.com两部分,绑定两个域名, 我们知道在没有绑定域名的时候前后台可以同步登录和退出,但 ...
- asp.net关于Cookie跨域(域名)的问题
Cookie是一个伟大的发明,它允许Web开发者保留他们的用户的登录状态.但是当你的站点有一个以上的域名时就会出现问题了.在Cookie规范上 说,一个cookie只能用于一个域名,不能够发给其它的域 ...
- .Net 站点跨域问题及解决方法
一.什么是站点跨域 了解跨域之前, 先了解下什么同源策略?百度百科:同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功 ...
- Appscan漏洞之跨站点请求伪造(CSRF)
公司前段时间使用了Fortify扫描项目代码,在修复完这些Fortify漏洞后,最近又启用了Appscan对项目代码进行漏洞扫描,同样也是安排了本人对这些漏洞进行修复.现在,针对修复过的Appscan ...
- IIS下设置跨域访问问题--Access-Control-Allow-Origin 站点跨域请求的问题
背景: 最近 开发中遇到新需求,把公司的OA系统迁移一套到小程序上面去 有些功能的信息是在小程序 查看 但是文件是在pc端上传的 例如:领导在外出办公 使用小程序查看xxxx.pdf文件 这个时候就 ...
- 跨站点脚本攻击XSS
来源:http://www.freebuf.com/articles/web/15188.html 跨站点脚本攻击是一种Web应用程序的攻击,攻击者尝试注入恶意脚本代码到受信任的网站上执行恶意操作.在 ...
- 跨站点请求伪造 - SpringBoot配置CSRF过滤器
1. 跨站点请求伪造 风险:可能会窃取或操纵客户会话和 cookie,它们可能用于模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务. 原因:应用程序使用的认证方法不充分. ...
随机推荐
- 【转】Android 4.0.3 CTS 测试
原文网址:http://blog.csdn.net/zxm317122667/article/details/8508013 Android-CTS 4.0.3测试基本配置 1. Download C ...
- C++标准:C++不允许修改任何基本型别(包括指针)的暂时值
从<C++标准库>一书中看到这样一句话:C++不允许修改任何基本型别(包括指针)的暂时值,想了半天,实在不理解.基本类型char,int,float等等还有暂时值?例如int a=2,那么 ...
- 方案:手动升级WordPress系统
对于WordPress系统及时进行更新维护是十分必须的操作,更新维护不仅可以更新系统服务功能,还能够完善安全系统. 如果你是虚拟主机的用户,可以使用FTP账户进行自动更新服务,但是如果你是V ...
- poj3299
...
- c++之 数组
数组的定义 数组用于表示一组数值,例如: char arr[5]; 其中,arr称为"数组变量",简称"数组".它表示5个char型数据,我们把每一个数据称为一 ...
- Android学习总结——欢迎页和导航页的实现
activity_welcome.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayo ...
- redmine fastcgi常常崩溃的解决方式
最终找到了解决方法,在以下的文件里加入两行就可以: /home/redmine/redmine-2.5.1/public/dispatch.fcgi require 'rubygems' requir ...
- 海量路由表能够使用HASH表存储吗-HASH查找和TRIE树查找
千万别! 非常多人这样说,也包括我. Linux内核早就把HASH路由表去掉了.如今就仅仅剩下TRIE了,只是我还是希望就这两种数据结构展开一些形而上的讨论. 1.hash和trie/radix ha ...
- C++ Primer笔记1_转义字符_标准库类型string_标准库类型vector
1.转义字符 一般有两种方式: \x后紧跟1个或多个十六进制数字.或\后紧跟1.2.3个八进制数字,当中数字部分是字符相应的数值. #include <iostream> using na ...
- java实现各种数据统计图(柱形图,饼图,折线图)
近期在做数据挖掘的课程设计,须要将数据分析的结果非常直观的展现给用户,这就要用到数据统计图,要实现这个功能就须要几个第三方包了: 1. jfreechart-1.0.13.jar 2. ...