完全跨站点跨域名单点(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,它们可能用于模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务. 原因:应用程序使用的认证方法不充分. ...
随机推荐
- Hdu2437-Jerboas(取余数判重搜索)
Jerboas are small desert-living animals, which resemble mice with a long tufted tail and very long h ...
- OpenStack Mixture HypervisorsDriver configure and implementation theory
通过本文,您将可以了解在 OpenStack 中如何进行混合 Hypervisor 的配置及其实现原理的基本分析.本文主要结合作者在 Nova 中的实际开发经验对 OpenStack 中混合 Hype ...
- 平时的笔记04:处理stagger模块
#! /usr/bin/env python3 # # __init__.py # From the stagger project: http://code.google.com/p/stagger ...
- Highcharts 非常实用的Javascript统计图
Highcharts 官网: http://www.highcharts.com Highcharts 官网示例:http://www.highcharts.com/demo/ Highcharts ...
- 分析NTFS文件系统得到特定文件的内容
找某一个文件的内容(如要读取文件D:\dir\dir2\text.txt,详细过程例如以下: (1)读取分区表/分区链表信息,找到磁盘F的起始扇区. (2)读取D盘的第一个扇区(分区的BOOTSETO ...
- Leetcode 238 Product of Array Except Self 时间O(n)和空间O(1)解法
1. 问题描写叙述 给定一个n个整数的数组(n>1n>1)nums,返回一个数组output,当中的元素outputioutput_i的值为原数组nums中除numsinums_i之外的全 ...
- JMeter一个错误the target server failed to respond--JMeter坑
问题:1.在测试一个http景象,特别是集波动TPS时刻,出现了一个错误.它现在是一个必须错误(压力顺利时却零星的错误,甚至很少见): 每次必现错误(開始一直怀疑是网络或程序的问题) 2.失败事务 ...
- phonegap环境配置与基本操作
一.开发环境配置: 1.工具环境安装: 安装java sdk 1.6以上版本号,Android Development Tools.ant,系统变量 Path后面加入 新增名稱 JAVA_HOME 值 ...
- ZOJ 3329 One Person Game 带环的概率DP
每次都和e[0]有关系 通过方程消去环 dp[i] = sigma(dp[i+k]*p)+dp[0]*p+1 dp[i] = a[i]*dp[0]+b[i] dp[i] = sigma(p*(a[i+ ...
- js将对象转成字符串-支持微信
最近写一个微信项目时用到了 把对象转成字符串,因为我需要把它存在cookie中,碰到了一些问题,在这里分享一下. 要转换的就是这货~ var FBinf = { "workPlacesCod ...