技术要点:COOKIE跨域 + SESSION共享
cookie跨域:让不同域下的session cookie有着同样的session id
session共享:同一会话系统,客户端不同域下的session id相同故可访问相同的会话状态
完全跨域的网站架构和业务分配在日常生活中并不少见,你每天上淘宝去支付宝付款,还有我经常上的大B站,出来个biligame的业务,将原来B站的跨二级域单点登录提升到了完全跨域的高度
=====================================================================
1、p3p协议(cookie跨域的根基)
此协议可以完成cookie的跨域设置,即我在A域下请求B域的某个方法,B域上加载此协议后可以实现通过A域的请求完成cookie设置【当然此cookie的域肯定是B域的】,说的简单些就是A域的cookie只能是在A域下设置,再怎样你也做不到在A域下设置一个B域的cookie,但你可以通过A域向B域发送一个跨域请求,B域响应此请求,虽然此请求来至A域,在某些情况下(IE大法)依旧无法完成cookie的设置,但在P3P协议的帮助下,即B域上运行着P3P协议,就可以完美的接收来至任何他域的请求来设置cookie了【再次强调,B域的cookie只能在B域下设置,若A域下就能设置B域的cookie先不说SOP协议当然无存,我们还没事折腾什么跨域】,门已开,肯定得需要安检,这就涉及到B域对外域请求做合法性检查的事情了
在A域下不能设置域为B域的cookie,域为X的cookie只能在X域下被设置,但此请求可以通过外域发起,A向B发起,B验证接收相关数据设置域为B的cookie,虽然用户目前还没有浏览B,但本地已经生成了B域的cookie
可以看到在此域下设置的外域的cookie没有"生效",可能有人说当前域只能访问当前域下的cookie,很好,我们继续看下面的与此cookie对应的域是否能访问到,再没有那就可以证明了,而且此域下通过script标签发起跨域请求
此域运行了p3p协议,响应了来至www.sallency.com的请求,根据传递的参数设置cookie,这样就完成了cookie跨域设置,即我并没有访问www.yii.com,但我客户端已经生成一个此域的cookie,是我从www.sallency.com跨域完成生成的
=====================================================================
2、session共享
session共享的根本所在就是要让每个域下的session_id相同
二级跨域只需要把session cookie domain设定为一级域,这样每个二级域都使用此一级域的session cookie作为session id去读取服务器端的会话状态,就实现了单点登录
完全跨域则需要跨域设置相同的session cookie
session是存储在服务器端与用户本地的session cookie挂钩的用户对话数据,当我们每次向服务器发送请求时都会携带本地的cookie,服务端session_start后会检查请求中有无session cookie的键值,有则根据此键值去session系统读取对应的数据载入内存,就形成了$_SESSION超全局变量,若没有则在客户端本地生成域为当前域的session cookie(注意cookie的匹配策略同样为最长匹配,www.sallency.com可访问域为www.sallency.com和.sallency.com的cookie,但会选取最长匹配域作为最后值 )
可以看出相同键值下会做域的最长匹配,这其实也是二级跨域时为什么要把服务器的session.cookie_domain设定为父域,否则每次请求服务器session_start时服务器会按session.cookie_domain的域去检测有没有此域的session cookie的键值,有那就是session_id,没有服务器会设置一个当前二级域的session cookie,每次请求的session id就成为此二级域自己重新生成的,那就达不到我们目的了,我们的目的是服务器将一级域作为session cookie的域,这样不论哪个子域都会把域为一级域的session cookie的session_id作为键值去读取服务器端的session
session共享一般是使用数据库模式,共用一个数据库系统作为session系统,我为了方便使用的redis模式,很简单,没什么可讲的
=====================================================================
3、代码实现
index.php 主界面
<?php
error_reporting(E_ALL);
//使用redis做session共享
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://192.168.30.154:6379');
session_start();
//退出 销毁服务器端的session 状态完全同步 其他站点也会退出登录
if (isset($_GET['act'])) {
session_destroy();
header("location:index.php");
}
//添加新的session 用以验证session完全同步
if (isset($_POST['add'])) {
$_SESSION[$_POST['key']] = $_POST['value'];
header("location:index.php");
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>www.asite.com</title>
</head>
<body>
<?php
if (isset($_SESSION['user'])) {
?>
<div>welcome!<?php echo $_SESSION['user']?></div>
<div>
<h4>session完全同步验证</h4>
<?php foreach ($_SESSION as $key => $value): ?>
<p><?php echo $key . '--' . $value?></p>
<?php endforeach ?>
</div>
<div>
<h4>添加新的session键值</h4>
<form action="" method="post">
<input type="text" name="key" placehodler="session key">
<input type="text" name="value" placehodler="session value">
<input type="submit" value="Add" name="add">
</form>
</div>
<a href="index.php?act=logout">退出</a>
<?php
} elseif (isset($_POST['submit'])) {
//用户登录
$_SESSION['user'] = $_POST['user'];
$_SESSION['passwd'] = $_POST['passwd'];
//同时触发cookie跨域设置的请求
header("location:p3p.php");
} else {
?>
<div>
<h4>登录<h4>
<form action="" method="post">
<input type="text" name="user" placehodler="username">
<input type="text" name="passwd" placehodler="userpasswd">
<input type="submit" value="Login" name="submit">
</form>
</div>
<?php
}
?>
</body>
</html>
p3p.php 完成跨域cookie设置请求
asite肯定是请求bsite设置好和自己session name session id相同的session cookie
<?php
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
if (isset($_GET['sessname']) && isset($_GET['sessid'])) {
// cross domain request from www.bsite.com
setcookie($_GET['sessname'], $_GET['sessid'], time() + 3600, '/', 'www.asite.com');
header("location:index.php");
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>asite cross domain set bsite cookie</title>
<?php
//在这里我没有做session redis共享的一些初始化设置,因为我不并不去读取当前session的数据内容
//我只需把session name 和 session id 跨域请求并传递给bsite,其会根据我传递的参数在其域下设定相同的session cookie
session_start();
echo "<script src='http://www.bsite.com/p3p.php?sessid=" . session_id() . "&sessname=". session_name() ."'></script>";
?>
</head>
<body>
<div style="text-align: center">
<p>success login</p>
</div>
</body>
<script type="text/javascript">
window.onload = function() {
setTimeout(function(){
window.location.replace('index.php');
}, 1000);
}
</script>
</html>
=====================================================================
index.php 与asite index.php不同之处就在于页面title不一样...方便识别嘛....
<?php
error_reporting(E_ALL);
//使用redis做session共享
ini_set('session.save_handler', 'redis');
ini_set('session.save_path', 'tcp://192.168.30.154:6379');
session_start();
//退出 销毁服务器端的session 状态完全同步 其他站点也会退出登录
if (isset($_GET['act'])) {
session_destroy();
header("location:index.php");
}
//添加新的session 用以验证session完全同步
if (isset($_POST['add'])) {
$_SESSION[$_POST['key']] = $_POST['value'];
header("location:index.php");
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>www.bsite.com</title>
</head>
<body>
<?php
if (isset($_SESSION['user'])) {
?>
<div>welcome!<?php echo $_SESSION['user']?></div>
<div>
<h4>session完全同步验证</h4>
<?php foreach ($_SESSION as $key => $value): ?>
<p><?php echo $key . '--' . $value?></p>
<?php endforeach ?>
</div>
<div>
<h4>添加新的session键值</h4>
<form action="" method="post">
<input type="text" name="key" placehodler="session key">
<input type="text" name="value" placehodler="session value">
<input type="submit" value="Add" name="add">
</form>
</div>
<a href="index.php?act=logout">退出</a>
<?php
} elseif (isset($_POST['submit'])) {
//用户登录
$_SESSION['user'] = $_POST['user'];
$_SESSION['passwd'] = $_POST['passwd'];
//同时触发cookie跨域设置的请求
header("location:p3p.php");
} else {
?>
<div>
<h4>登录<h4>
<form action="" method="post">
<input type="text" name="user" placehodler="username">
<input type="text" name="passwd" placehodler="userpasswd">
<input type="submit" value="Login" name="submit">
</form>
</div>
<?php
}
?>
</body>
</html>
p3p.php 完成跨域cookie设置请求 bsite肯定是请求asite设置好和自己session name session id相同的session cookie
<?php
header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"');
if (isset($_GET['sessname']) && isset($_GET['sessid'])) {
// cross domain request from www.asite.com
setcookie($_GET['sessname'], $_GET['sessid'], time() + 3600, '/', 'www.bsite.com');
header("location:index.php");
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>bsite cross domain et asite cookie</title>
<?php
//在这里我没有做session redis共享的一些初始化设置,因为我不并不去读取当前session的数据内容
//我只需把session name 和 session id 跨域请求并传递给asite,其会根据我传递的参数在其域下设定相同的session cookie
session_start();
echo "<script src='http://www.asite.com/p3p.php?sessid=" . session_id() . "&sessname=". session_name() ."'></script>";
?>
</head>
<body>
<div style="text-align: center">
<p>success login</p>
</div>
</body>
<script type="text/javascript">
window.onload = function() {
setTimeout(function(){
window.location.replace('index.php');
}, 1000);
}
</script>
</html>
=====================================================================
效果
可以看到asite和bsite的session cookie的session id是相同的,所以二者会读取服务器上的同一session数据