Php开发完全跨站点跨域名单点(SSO)同步登录和注销
From:http://www.cnblogs.com/JinkoWu/p/5056646.html
先来说说什么是单点登录(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:

1 <?php
2 /**
3 * Created by PhpStorm.
4 * by Jinko Wu
5 * Date: 2015/12/17
6 */
7 error_reporting(E_ALL ^ E_NOTICE);
8 require "session.lib.php";
9 $session = jsession_start();
10
11 echo '<meta charset="utf-8"/>';
12 if(isset($session)) {
13 echo 'A 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.jsession_id().'">退出</a>';
14 } else {
15 echo 'A 您还没有登录!'.'<a href="http://www.a.cn/login.php">去登录</a>';
16 }

www.a.cn/login.php:

1 <?php
2 /**
3 * Created by PhpStorm.
4 * by Jinko Wu
5 * Date: 2015/12/17
6 */
7 ?>
8 <meta charset="utf-8"/>
9 <form action="session-api.php?action=login" method="post">
10 <input type="text" name="name">
11 <input type="hidden" name="redirect" value="<?php echo $_SERVER['HTTP_REFERER'] ?>">
12 <input type="submit">
13 </form>

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

1 <?php
2 /**
3 * Created by PhpStorm.
4 * by Jinko Wu
5 * Date: 2015/12/17
6 */
7 function jsession_start()
8 {
9 if(!$_COOKIE['__SESSID']) {
10 jsession_regenerate_id();
11 }
12
13 return jsession_update();
14 }
15
16 function jsession_update($session_id=null)
17 {
18 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id;
19
20 if($session_id == '') {
21 return null;
22 }
23
24 if($session_id && $data = jsession_is_valid($session_id)) {
25 jsession_save($data);
26 setcookie('__SESSID', $session_id, time()+jsession_live_time());
27 return $data;
28 }
29
30 return null;
31 }
32
33 function jsession_regenerate_id()
34 {
35 $sessid = jsession_generate_id();
36
37 if(jsession_id() != '' && file_exists('.sessioncache/' .jsession_id())) {
38 rename('.sessioncache/' .jsession_id(), '.sessioncache/' .$sessid);
39 }
40
41 $_COOKIE['__SESSID'] = $sessid;
42 setcookie('__SESSID', $sessid, time()+jsession_live_time());
43 }
44
45 function jsession_generate_id()
46 {
47 return 's'.base_convert(rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999).rand(0, 9999999999), 10, 36);
48 }
49
50 function jsession_id()
51 {
52 return $_COOKIE['__SESSID'];
53 }
54
55 function jsession_live_time()
56 {
57 $gc_maxlifetime = ini_get('session.gc_maxlifetime');
58 $gc_maxlifetime = $gc_maxlifetime == '' ? 1440 : $gc_maxlifetime;
59 return $gc_maxlifetime;
60 }
61
62 function jsession_is_valid($session_id)
63 {
64 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id;
65
66 if($session_id == '') {
67 return false;
68 }
69
70 if(file_exists('.sessioncache/' .$session_id)) {
71 $data = unserialize(@file_get_contents('.sessioncache/' . $session_id));
72 return time() <= $data['time'] ? $data['data'] : false;
73 } else {
74 return false;
75 }
76 }
77
78 function jsession_data($session_id=null)
79 {
80 $session_id = $session_id === null ? $_COOKIE['__SESSID'] : $session_id;
81
82 if($session_id == '') {
83 return null;
84 }
85
86 if($data = jsession_is_valid($session_id)) {
87 return $data;
88 }
89
90 return null;
91 }
92
93 function jsession_save($data)
94 {
95
96 if(!is_dir('.sessioncache')) {
97 mkdir('.sessioncache', 0777);
98 }
99
100 if($_COOKIE['__SESSID'] == '') {
101 return null;
102 }
103
104 $file = '.sessioncache/'.$_COOKIE['__SESSID'];
105 $fp = fopen($file , 'w');
106
107 if(flock($fp , LOCK_EX)) {
108 fwrite($fp, serialize(array('time' => time() + jsession_live_time(), 'data' => $data)));
109 flock($fp, LOCK_UN);
110 }
111
112 fclose($fp);
113 return $data;
114 }
115
116 function jsession_destory($session_id)
117 {
118 if($session_id == '') {
119 return ;
120 }
121
122 if($session_id == $_COOKIE['__SESSID']) {
123 setcookie('__SESSID', '');
124 $_COOKIE['__SESSID'] = null;
125 }
126
127 if(file_exists('.sessioncache/' .$session_id)) {
128 @unlink('.sessioncache/' .$session_id);
129 }
130 }

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

1 <?php
2 /**
3 * Created by PhpStorm.
4 * by Jinko Wu
5 * Date: 2015/12/17
6 */
7 error_reporting(E_ALL ^ E_NOTICE);
8 require 'session.lib.php';
9
10 if($_REQUEST['action'] == 'check') {
11 $session = jsession_is_valid($_REQUEST['id']);
12 echo json_encode(array('session' => $session));
13
14 } else if($_REQUEST['action'] == 'logout') {
15 if($_REQUEST['sessid'] !== null) {
16 jsession_destory($_REQUEST['sessid']);
17 }
18
19 echo '<meta charset="utf-8"/>';
20 echo '退出登录成功, 正在跳转...';
21 $_SERVER['HTTP_REFERER'] = $_SERVER['HTTP_REFERER'] == '' ? '/' : $_SERVER['HTTP_REFERER'];
22 echo '<script type="text/javascript">window.location.href = "' . $_SERVER['HTTP_REFERER'] . '";</script>';
23
24 } else if($_REQUEST['action'] == 'login') {
25 jsession_start();
26 $data = jsession_save(array('name' => trim($_REQUEST['name'])));
27 $redirect = $_REQUEST['redirect'] ? $_REQUEST['redirect'] : 'http://www.a.cn';
28 echo '<meta charset="UTF-8"><script type="text/javascript">window.location.href = "'.$redirect.'";</script>';
29
30 } else {
31 $session = jsession_start();
32
33 if($session && trim($_REQUEST['call']) != '' && jsession_id() != '') {
34 echo $_REQUEST['call'] . '('.json_encode(array('sessid' => jsession_id(), 'session' => $session)).')';
35 }
36 }

www.b.net/index.php:

1 <?php
2 /**
3 * Created by PhpStorm.
4 * by Jinko Wu
5 * Date: 2015/12/17
6 */
7 ?>
8 <meta charset="utf-8"/>
9 <script type="text/javascript">
10 function setCookie(name,value)
11 {
12 var Days = 30;
13 var exp = new Date();
14 exp.setTime(exp.getTime() + Days*24*60*60*1000);
15 document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();
16 }
17
18 //jsonp 登录函数
19 function jsonp_do_login(data)
20 {
21 document.getElementById('name').innerHTML = 'B 您好:' + data.session.name + '<a href="http://www.a.cn/session-api.php?action=logout&sessid='+data.sessid+'">退出</a>';
22 console.log(data);
23 setCookie('__SESSID', data.sessid);
24 }
25 </script>
26 <?php
27 error_reporting(E_ALL ^ E_NOTICE);
28 session_start();
29 $session = check_session();
30 $sessid = $_COOKIE['__SESSID'];
31
32 if($session) {
33 echo 'B 您好:' . $session['name'] . '<a href="http://www.a.cn/session-api.php?action=logout&sessid='.$sessid.'">退出</a>';
34 } else {
35 echo '<span id="name">B 您还没有登录!<a href="http://www.a.cn/login.php">去登录</a></span>';
36 }
37
38 function check_session()
39 {
40 $sessid = $_COOKIE['__SESSID'];
41 $json = file_get_contents("http://www.a.cn/session-api.php?id=$sessid&action=check");
42 $json_data = json_decode($json, true);
43
44 if($json_data == null || empty($json_data['session'])) {
45 return false;
46 } else {
47 return $json_data['session'];
48 }
49 }
50
51 ?>
52
53 <?php if(!$session): ?>
54 <script type="text/javascript" src="http://www.a.cn/session-api.php?call=jsonp_do_login&<?php echo rand()?>"></script>
55 <?php endif; ?>

点击这里下载打包好的代码: http://files.cnblogs.com/files/JinkoWu/MultiSiteSingleLogin.zip
最后附上一张示例图片:

Php开发完全跨站点跨域名单点(SSO)同步登录和注销的更多相关文章
- 完全跨站点跨域名单点(SSO)同步登录和注销
先来说说什么是单点登录(SSO).来自百科的介绍:SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.它包括可以将这次主 ...
- 基于Swift语言开发微信、QQ和微博的SSO授权登录代码分析
前言 Swift 语言,怎么说呢,有一种先接受后排斥.又欢迎的感觉,纵观国外大牛开源框架或项目演示,Swift差点儿占领了多半,而国内尽管出现非常多相关技术介绍和教程,可是在真正项目开发中使用的占领非 ...
- 网站跨站点单点登录实现--cookie
至于什么是单点登录,举个例子,如果你登录了msn messenger,访问hotmail邮件就不用在此登录.一般单点登录都需要有一个独立的登录站点,一般具有独立的域名,专门的进行注册,登录,注销等操作 ...
- IIS下设置跨域访问问题--Access-Control-Allow-Origin 站点跨域请求的问题
背景: 最近 开发中遇到新需求,把公司的OA系统迁移一套到小程序上面去 有些功能的信息是在小程序 查看 但是文件是在pc端上传的 例如:领导在外出办公 使用小程序查看xxxx.pdf文件 这个时候就 ...
- 跨站点端口攻击 – XSPA(SSPA)
许多Web应用程序提供的功能将数据从其他Web服务器,由于种种原因.下载XML提要,从远程服务器,Web应用程序可以使用用户指定的URL,获取图像,此功能可能会被滥用,使制作的查询使用易受攻击的Web ...
- asp.net关于Cookie跨域(域名)的问题
Cookie是一个伟大的发明,它允许Web开发者保留他们的用户的登录状态.但是当你的站点有一个以上的域名时就会出现问题了.在Cookie规范上 说,一个cookie只能用于一个域名,不能够发给其它的域 ...
- ASP.NET Core中的OWASP Top 10 十大风险-跨站点脚本攻击 (XSS)
不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: https://dotnetcoretutorials.com/201 ...
- IBM Rational AppScan:跨站点脚本攻击深入解析
IBM Rational AppScan:跨站点脚本攻击深入解析 了解黑客如何启动跨站点脚本攻击(cross-site scripting,XSS),该攻击危害(及不危害)什么,如何检测它们,以 ...
- vue 开发和生产的跨域问题
开发阶段 在开发环境与后端调试的时候难免会遇到跨域问题,在 vue 项目中常用的是 proxyTable,这个用起来很方便. 打开 config 文件夹下面的 index.js,找到 dev 开发模式 ...
随机推荐
- Git本地版本控制备忘
首先git是一个版本控制工具,类似于SVN 笔记包括两部分,git本地版本控制和git远程协助 一.Git本地版本控制 以git windows版本msysgit为例,下载地址http://msysg ...
- xcode插件——新建cocos2dx工程
个人制作的一个创建cocos2dx工程的xcode小插件 按照readme安装一下即可. 创建工程后,将自动弹出finder到工程目录. 弹出窗口:
- linux内存管理--伙伴系统和内存分配器
3.1页框的管理 所有的页框描述符都存放在mem_map数组中. 3.1.1page数据结构 struct page { page_flags_t flags; //标志 atomic_t _coun ...
- python linecache标准库基础学习
#python标准库基础之:linecacge:高效读取文本文件#说明与作用"""可以从文件或者导入python模块获取文件,维护一个结果缓存,从而可以更高效地从相同文件 ...
- python socket学习
import socket localip=socket.gethostbyname(socket.gethostname()) print (localip) iplist=socket.getho ...
- The secret of ROWID
表里每个数据行都有一个行头部,在这里存放了该行数据所包含的列的数量,以及锁定标记等.当某个事务更新某条记录时,会在该数据行的头部记录所用到的ITL槽号以及锁定标记.接下来则是列长度以及列的值.Orac ...
- Cocos2d-x学习笔记(3)
Cocos2d-x有一个包括全部其它头文件的cocos2d.h,仅仅要在使用时包括这个头文件,就能够使用引擎的全部功能.Cocos2d-x的类都放置于cocos2d的命名空间下,如引擎下的" ...
- Android ActionBar应用实战,高仿微信主界面的设计
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/26365683 经过前面两篇文章的学习,我想大家对ActionBar都已经有一个相对 ...
- SQL数据库注入防范 ASP.NET Globle警告
在项目中的Global.asax页面代码中加下面的代码,就可以有效的防范简单的SQL注入. protected void Application_BeginRequest(Object sender, ...
- 《第一行代码》学习笔记13-UI(2)
1.EditText:程序和用户进行交互的重要控件,允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理. 2.Android控件使用的一般规律:给控件定义一个id->指定下控件的宽 ...