有四个网站分别为:

www.a.com
www.b.com
www.c.com
www.sso.com
需求是如果我们在sso登陆后,其他网站也会显示登陆中,不需要重复登陆,退出时,其他网站也会失效。
 
解决流程如下:
1、我们需要统一这四个站的session存储方式。 (session共享,保存到mysql中)
2、登陆和退出的请求统一由sso这个站处理。(其他站全部跳转到sso上)
3、当用户在sso.com登陆时,我们在验证完用户名和密码正确后,通过获取session_id和生成一段skey加密串保存到user表的相应字段中。然后跳转到一个中间页面,访页面通过iframe的方式引入a.com,b.com,和c.com。
<iframe src="http://www.a.com/sso.php?sessid=xxx&skey=xxx" width="0" height="0" frameborder="0">
</iframe>
<iframe src="http://www.b.com/sso.php?sessid=xxx&skey=xxx" width="0" height="0" frameborder="0">
</iframe>
<iframe src="http://www.c.com/sso.php?sessid=xxx&skey=xxx" width="0" height="0" frameborder="0">
</iframe>
(*传给a,b,c这三个站的sessid是经过加密的,为了安全。)
 
4、a,b,c这三个站的sso.php拿到sessid和skey解密后得到session_id,然后到user表中进行比对,如果正确,设置当前会话的session_id,然后session_start(),因为四个站的session是共享的,所以a,b ,c三个站就拿到了在sso上登陆时存储的会话数据。
//sessid是sso.com上产生的session_id
session_id(sessid);
session_start();
具体的代码如下:
www.sso.com/index.php
<?php
header('Content-Type:text/html;charset=utf-8');
require_once './DBSession.php';
session_start(); if(array_key_exists('userInfo', $_SESSION) && !empty($_SESSION['userInfo'])) {
$userInfo = $_SESSION['userInfo'];
echo '欢迎', $userInfo['name'], '<br />';
echo '<a href="./logout.php">退出</a>';
} else {
header('Location: ./login.html');
}
www.sso.com/login.html
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<style>
* {padding:0;margin:0;}
.loginForm {width:450px;height:auto;margin:100px auto 0 auto;}
.loginForm table {width:100%;border-collapse:collapse;}
.loginForm table td, .loginForm table th {border:1px solid #dcdcdc;padding:5px;}
</style>
</head>
<body>
<form action="./login.php" method="post" class="loginForm">
<table>
<tr>
<th colspan="2">用户登陆</th>
</tr>
<tr>
<td>用户名:</td>
<td><input type="text" name="uname" value="" /></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="upwd" value="" /></td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="submit" value="登陆" /></td>
</tr>
</table>
</form>
</body>
</html>

www.sso.com/login.php

<?php
require_once './DBSession.php';
require_once './DB.php';
session_start(); if(!empty($_POST)) {
$uname = !empty($_POST['uname']) ? trim($_POST['uname']) : '';
$upwd = !empty($_POST['upwd']) ? trim($_POST['upwd']) : ''; $upwd = md5($upwd);
$ret = mysql_query("select `id`,`name` from `user` where `name`='{$uname}' and `pwd`='{$upwd}';", $db);
$data = mysql_fetch_assoc($ret);
if($data) {
$userInfo = array(
'id' => $data['id'],
'name' => $data['name'],
);
$_SESSION['userInfo'] = $userInfo;
//通过用户ID,用户名和时间戳的md5生成skey
$skey = md5($data['id'] . $data['name'] . time());
$sessId = session_id(); $userId = $data['id'];
$ret = mysql_query("update `user` set `sessid`='$sessId', `skey`='$skey' where `id`={$userId};", $db);
if(mysql_affected_rows($db)) { //取skey前三个字符和后三个字符生成key
$key = substr($skey, 0, 3) . substr($skey, -1, 3);
$lifeTime = ini_get('session.gc_maxlifetime');
$sessId = authcode($sessId, 'ENCODE', $key, $lifeTime); $appSSOUrl = array(
'http://www.a.com/sso.php',
'http://www.b.com/sso.php',
'http://www.c.com/sso.php',
); require 'iframe.php';
}
} else {
header('Location: ./login.html');
}
}
www.sso.com/iframe.php
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<?php foreach($appSSOUrl as $item) { ?>
<iframe src="<?php echo $item . '?sessid=' . $sessId . '&skey=' . $skey;?>" width="0" height="0" frameborder="0"></iframe>
<?php } ?>
</body>
<script type="text/javascript">
window.onload = function() {
location.href = 'index.php';
};
</script>
</html>

www.sso.com/logout.php

<?php
require_once './DBSession.php';
require_once './DB.php'; session_start(); if(array_key_exists('userInfo', $_SESSION) && !empty($_SESSION['userInfo'])) {
$uid = $_SESSION['userInfo']['id']; $ret = mysql_query("update `user` set `sessid`='', `skey`='' where id={$uid}", $db);
if(session_destroy()) {
header('Location: ./login.html');
}
}
www.a.com/index.php
<?php
header('Content-Type:text/html;charset=utf-8');
require_once './DBSession.php';
session_start(); if(array_key_exists('userInfo', $_SESSION) && !empty($_SESSION['userInfo'])) {
$userInfo = $_SESSION['userInfo'];
echo '欢迎', $userInfo['name'], '<br />';
echo '<a href="http://www.sso.com/logout.php">退出</a>';
} else {
header('Location: http://www.sso.com/login.html');
}

www.a.com/sso.php

<?php
require_once './DBSession.php';
require_once './DB.php'; $sessid = !empty($_GET['sessid']) ? trim($_GET['sessid']) : '';
$skey = !empty($_GET['skey']) ? trim($_GET['skey']) : ''; if(empty($sessid) || empty($skey)) {
exit;
} $key = substr($skey, 0, 3) . substr($skey, -1, 3);
$sessid = authcode($sessid, 'DECODE', $key);
if(!empty($sessid)) {
$ret = mysql_query("select count(*) as num from user where sessid='{$sessid}' and skey='{$skey}'");
$row = mysql_fetch_assoc($ret);
if($row['num']) {
session_id($sessid);
session_start();
}
} else {
header('Location: http://www.sso.com/login.html');
}

b和c站的文件跟a站是一样的,只需复制a站即可,这里就不重复了。

 
DBSession.php代码:
<?php
class DBSession {
protected $db = null;
protected $lifeTime = 0;
protected $sessTable = ''; public function __construct($db, $sessTable) {
$this->db = $db;
$this->sessTable = $sessTable;
$this->lifeTime = ini_get('session.gc_maxlifetime'); ini_set('session.save_handler', 'user');
session_set_save_handler(
array($this, "open"),
array($this, "close"),
array($this, "read"),
array($this, "write"),
array($this, "destroy"),
array($this, "gc")
);
register_shutdown_function('session_write_close');
} public function open($savePath, $sessName) {
return true;
} public function close() {
$this->gc($this->lifeTime);
return true;
} public function read($sessId) {
$time = time();
$ret = mysql_query("SELECT `data` FROM `{$this->sessTable}` WHERE `sid`='{$sessId}' AND `expire` > {$time};", $this->db);
if($ret) {
$row = mysql_fetch_assoc($ret);
return $row['data'];
}
return '';
} public function write($sessId, $sessData) {
$expire = time() + $this->lifeTime;
$sessData = mysql_real_escape_string($sessData); $ret = mysql_query("SELECT COUNT(*) AS cnt FROM `{$this->sessTable}` WHERE `sid`='{$sessId}';", $this->db);
$row = mysql_fetch_assoc($ret);
if($row['cnt']) {
$sql = "UPDATE `{$this->sessTable}` SET `data`='{$sessData}', `expire`={$expire} WHERE `sid`='{$sessId}';";
} else {
$sql = "INSERT INTO `{$this->sessTable}` (`sid`,`expire`,`data`) VALUES('{$sessId}',{$expire},'{$sessData}');";
}
mysql_query($sql, $this->db);
if(mysql_affected_rows($this->db)) {
return true;
}
return false;
} public function destroy($sessId) {
mysql_query("DELETE FROM `{$this->sessTable}` WHERE `sid`='{$sessId}';", $this->db);
if(mysql_affected_rows($this->db)) {
return true;
}
return false;
} public function gc($lifeTime) {
$time = time();
mysql_query("DELETE FROM `{$this->sessTable}` WHERE `expire` < {$time};", $this->db);
return mysql_affected_rows($this->db);
}
} $sessDb = mysql_connect('127.0.0.1', 'root', '') or die('connect error');
mysql_select_db('test', $sessDb) or ('select db error');
mysql_query('set names utf8', $sessDb);
new DBSession($sessDb, 'session');
session表结构如下:
CREATE TABLE `session` (
`sid` varchar(32) NOT NULL DEFAULT '' COMMENT 'session_id',
`expire` int(11) NOT NULL COMMENT '过期时间',
`data` text COMMENT 'session数据',
PRIMARY KEY (`sid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='session表';
DB.php文件代码如下:
<?php
$db = mysql_connect('127.0.0.1', 'root', '') or die('connect error');
mysql_select_db('test', $db) or ('select db error');
mysql_query('set names utf8', $db);

user表结构如下:

CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名',
`pwd` varchar(32) NOT NULL DEFAULT '' COMMENT '用户密码(md5)',
`sessid` varchar(32) NOT NULL DEFAULT '' COMMENT 'session_id(未加密)',
`skey` varchar(32) NOT NULL DEFAULT '' COMMENT '安全码',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';
加密解密函数authcode(取自Discuz)代码如下:
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
if($operation == 'DECODE') {
$string = str_replace('[a]','+',$string);
$string = str_replace('[b]','&',$string);
$string = str_replace('[c]','/',$string);
}
$ckey_length = 4;
$key = md5($key ? $key : 'livcmsencryption');
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
$ustr = $keyc.str_replace('=', '', base64_encode($result));
$ustr = str_replace('+','[a]',$ustr);
$ustr = str_replace('&','[b]',$ustr);
$ustr = str_replace('/','[c]',$ustr);
return $ustr;
}
}
我们通过在sso.com上登陆
登成功后会跳转到index.php

然后我们访问a.com,b.com ,c.com查看

我们点击任一网站退出,其他网站都已退出。

原理其实很简单,我们统一了四个站的session存储方式,然后让a,b,c三个站拿到用户在sso上登陆的会话id。这样会话信息就共享了。不过实现的很粗糙,仅供学习使用。
 
参考资料:

http://www.zhihu.com/question/19779937

php单点登陆简单实现 (iframe方式)的更多相关文章

  1. 集成基于CAS协议的单点登陆

    相信大家对单点登陆(SSO,Single Sign On)这个名词并不感到陌生吧?简单地说,单点登陆允许多个应用使用同一个登陆服务.一旦一个用户登陆了一个支持单点登陆的应用,那么在进入其它使用同一单点 ...

  2. IM开发基础知识补课:正确理解前置HTTP SSO单点登陆接口的原理

    1.前言 一个安全的信息系统,合法身份检查是必须环节.尤其IM这种以“人”为中心的社交体系,身份认证更是必不可少. 一些PC时代小型IM系统中,身份认证可能直接做到长连接中(也就是整个IM系统都是以长 ...

  3. 在多点环境下使用cas实现单点登陆及登出

    CAS 介绍 CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目.CAS 具有以下特 ...

  4. 单点登陆(SSO)

    一.背景 在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便.但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要 ...

  5. 集成基于OAuth协议的单点登陆

    在之前的一篇文章中,我们已经介绍了如何为一个应用添加对CAS协议的支持,进而使得我们的应用可以与所有基于CAS协议的单点登陆服务通讯.但是现在的单点登陆服务实际上并不全是通过实现CAS协议来完成的.例 ...

  6. Lind.DDD.SSO单点登陆组件的使用(原创)

    回到目录 一般sso的说明 在Lind.DDD框架里,有对单点登陆的集成,原理就是各个网站去sso网站统一登陆授权,之后在sso网站将登陆的token进行存储,存储方式随你(cache,redis,m ...

  7. jQuery简单实现iframe的高度根据页面内容自适应的方法(转)

    本文实例讲述了jQuery简单实现iframe的高度根据页面内容自适应的方法.分享给大家供大家参考,具体如下: 方式1: //注意:下面的代码是放在和iframe同一个页面中调用 $("#i ...

  8. ASP.NET 单点登陆

    第一种:同主域但不同子域之间实现单点登陆 Form验证其实是基于身份cookie的验证.客户登陆后,生成一个包含用户身份信息(包含一个ticket)的cookie,这个cookie的名字就是在web. ...

  9. ASP.NET在不同情况下实现单点登陆(SSO)的方法

    第一种:同主域但不同子域之间实现单点登陆 Form验证其实是基于身份cookie的验证.客户登陆后,生成一个包含用户身份信息(包含一个ticket)的cookie,这个cookie的名字就是在web. ...

随机推荐

  1. C++多线程同步之Mutex(互斥量)

    原文链接: http://blog.csdn.net/olansefengye1/article/details/53086141 一.互斥量Mutex同步多线程 1.Win32平台 相关函数和头文件 ...

  2. Flex知识

    转载:http://www.cnblogs.com/xia520pi/archive/2011/12/11/2283851.html

  3. PHP流程控制 - if 语句

    PHP - if 语句 if 语句用于仅当指定条件成立时执行代码. 语法 if (条件) { 条件成立时要执行的代码; } 如果当前时间小于 20,下面的实例将输出 "Have a good ...

  4. 知乎日报 API的图片盗链问题

    由最近 基于vue的知乎日报单页应用 引发的问题 以及问题解决历程 通过 知乎日报API 基于vue做一个知乎日报的单页应用,在获取图片时存在一个图片盗链问题,图片无法加载 提示 403 错误, 最终 ...

  5. 20180201之Burp Suite Professional V1.7.31 相关英文翻译

    Burp Suite Professional V1.7.31 打嗝   套件  专业

  6. Win10交换Ctrl和大写键

    打开注册表 [HKEY_LOCAL_MacHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout] "Scancode Map" ...

  7. JAVA 整合 SSM (Spring + SpringMVC + MyBatis)

    < 一 > POM 配置文件 ( 如果出现 JAR 包 引入错误, 请自行下载 ) <project xmlns="http://maven.apache.org/POM/ ...

  8. 1.Log4j入门

    转自:https://blog.csdn.net/luohai859/article/details/52250807 日志是应用软件中不可缺少的部分, .Apache的开源项目log4j是一个功能强 ...

  9. html页面中iframe导致JavaScript失效

    <body onload=“reset()”> <div id="part1"> some thing here .... <div> < ...

  10. cnapckSurround c++builder Region 代码折叠快捷键

    C++Builder代码折叠 cnapckSurround c++builder Region 代码折叠快捷键,可以导入导出,IDE code edit,cnpack menu surround wi ...