单点登录(SSO)的实现—通行证的基本原理

来源:互联网 发布:灯带伴侣 淘宝 编辑:程序博客网 时间:2024/04/28 12:26
!!!一个人的成功来自于他的观念和思想!!!

单点登录(SSO)的实现—通行证的基本原理

2007-08-14 18:11:24 / 个人分类:PHP学习

问题起源:想做一个面向校园的网站,因为势单力薄。部分模块采用整合其它开源系统的方案,比如BBS系统和BLOG系统。首先面临的就是用户身份认证的方式。由于这些不是自己开发的系统,都分别有自己的用户系统,于是面临统一身份认证的过程。

以前看过企业级的Web service方案,主要是通过XML,SOAP,WSDL和UDDI来实现。将应用服务都注册到UDDI服务器中,通过SOAP协议使用XML传递信息(当然需经过加密)。由于涉及到很多服务部署的问题,用JAVA来做这样的项目肯定是再好不过的了。我的目的只是几个WEB系统的整合,肯定是要排除这么伟大的方案了。关于Web service有兴趣的朋友可以参考机械工业出版社出版的《Web Servides原理与研发实践》,里面有详细的介绍。

那么对于这样的小WEB系统的整合该怎么来实现?我们假设这是个从零开始的项目,除了自己开发的系统外,还要用到一些其它组织开发的开源系统:比如BLOG,CMS,BBS。这些系统都有各自的用户系统。要把他们整合到一块有个原则,就是尽量不要破坏或者修改这些系统。那么要实现统一身份认证,我们必须要有一个用户信息库,然后吧这个数据库的信息映射到那些子系统的数据库中,在大型项目中,一般都会独立出一台单独的用户信息服务器,大部分高校采用LDAP来存放用户信息,因为采用的是树状结构,对经常读取但很少修改的数据而言,它的性能是很高的。

LDAP用户库和各子系统用户库的映射有很多种方法,我这里只用最简单的直接映射,也就是帐号和密码都是相同的。

假设我的域名部署如下

http://news.domain.com 这是CMS系统的域名

http://bbs.domain.com 这是论坛的域名

http://blog.domain.com 这是博客域名

http://reg.domain.com 这是统一注册和登录页面的域名PHPChina 开源社区门户 a9z0A[ A_ G
首先是注册,我们让所有子系统注册页面都转向到一个注册页面上来(各种脚本语言都有转向函数),比如说当用户希望在http://blog.domain.com/reg.php注册是,reg.php把这个请求转向到http://reg.domain.com/reg.php.

在实现注册时,由于刚开始时候子系统并不多,注册时把用户注册信息写入主用户数据库的同时写入各子系统的用户库。以后若有新的子系统加入进来时可以通过帐号激活的方式来实现新系统的帐号激活。

用户登录的过程,可以参考如下来自IBM的图片

流程描述如下:(仅描述正常流程)

1. 用户使用在统一认证服务注册的用户名和密码(也可能是其他的授权信息,比如数字签名等)登陆统一认证服务;

2. 统一认证服务创建了一个会话,同时将与该会话关联的访问认证令牌返回给用户;

3. 用户使用这个访问认证令牌访问某个支持统一身份认证服务的应用系统;

4. 该应用系统将访问认证令牌传入统一身份认证服务,认证访问认证令牌的有效性;

5. 统一身份认证服务确认认证令牌的有效性;

6. 应用系统接收访问,并返回访问结果,如果需要提高访问效率的话,应用系统可选择返回其自身的认证令牌已使得用户之后可以使用这个私有令牌持续访问。

上面所说的令牌我在WEB引用中可以用COOKIE或者SEESION来实现。

例如通过COOKIE来实现

1.用户在统一登录页登陆,通过查询主用户数据库判断用户是否合法,若是,则注册该用户的唯一COOKIE标识(可以通过加密用户名和密码得到,网上有很多算法)。

2.用户进入某子系统时,先判断COOKIE是否注册,若注册了,则解密该COOKIE得到用户名和帐号,并判断合法性。

3.如果合法,则立刻在该子系统中注册(可在原子系统的登录脚本种抽出登录的部分做成一个函数)

使用COOKIE的优点就是简单,只要设置一下就可以实现COOKIE的跨子域传递

例如

setcookie(NC_USER_COOKIE, 用户名, 失效时间, 作用路径, ‘.domain.com’);

就能实现在所有.domain.com子域下的传递。

但COOKIE也有他的缺点,首先就是安全级别不高,要提防COOKIE劫持的威胁,其次就是它只能跨子域传递,而不能跨完全不同的域。比如说domain.com和fuck.com之间就不能传递。

然后再说下SESSION的方式,由于SESSION是存储在服务器端的,所以安全级别肯定要比COOKIE的级别高。但是由于SEESION存储的位置不同,造成了无法跨域传递。可以通过把SEESION村入数据库来解决这个问题。

由于PHP的SESSION需要用到标识SESSION的COOKIE,所以需要设置下COOKIE的作用域

ini_set(’session.cookie_domain’, ‘.domian.com’);

然后重写PHP的SESSION操作函数。

PHP 提供了session_set_save_handle() 函数来自定义 SESSION 的处理过程,先将 session.save_handler 改成 user

session_module_name(‘user’);

然后几可以重写SESSION的操作了,下面是PHP牛人NIO写的SESSION操作

define(‘MY_SESS_TIME’, 3600);    //SESSION 生存时长PHPChina 开源社区门户 E k4gp%I7Zxf @4oA
//类定义
F8|#rMRS)W7M0class My_SessPHPChina 开源社区门户L[#ax:Mw x/F
{PHPChina 开源社区门户{R{+/$|,S:q;B
     function init()PHPChina 开源社区门户%ZJ&HyNE6r%@z
     {PHPChina 开源社区门户a!l%U @ ?XTm)T
         $domain = ‘.infor96.com’;PHPChina 开源社区门户j;rK$B*U;Z!lI3a
         //不使用 GET/POST 变量方式PHPChina 开源社区门户9r,LK7wPy/qg:Eb
         ini_set(’session.use_trans_sid’,     0);
V#Vro+]%@F0         //设置垃圾回收最大生存时间
4K at/h6R/?~D |i!C0         ini_set(’session.gc_maxlifetime’,    MY_SESS_TIME);

         //使用 COOKIE 保存 SESSION ID 的方式PHPChina 开源社区门户&crb x:Z;N A$Ag
         ini_set(’session.use_cookies’,       1);PHPChina 开源社区门户;^$AV1D-e:y7bo
         ini_set(’session.cookie_path’,       ‘/’);PHPChina 开源社区门户s-j+l:_"w/`s
         //多主机共享保存 SESSION ID 的 COOKIEPHPChina 开源社区门户O+o0`1d7EL!t Xh
         ini_set(’session.cookie_domain’,     $domain);

         //将 session.save_handler 设置为 user,而不是默认的 files
)`?)vg b"bO$]r5h0         session_module_name(‘user’);
ao9~ m G#n2^&D8nb0         //定义 SESSION 各项操作所对应的方法名:
!Jh-Ks x.S0pa0         session_set_save_handler(
v0f| [C8PnX)o#^4?0             array(‘My_Sess’, ‘open’),    //对应于静态方法 My_Sess::open(),下同。
'U Oo*sa?"M0             array(‘My_Sess’, ‘close’),
.PW7t$s l I0             array(‘My_Sess’, ‘read’),
i;P.NlL K0             array(‘My_Sess’, ‘write’),
0e7`k8s(rV0             array(‘My_Sess’, ‘destroy’),PHPChina 开源社区门户XaH|M,d"y [
             array(‘My_Sess’, ‘gc’)
j`d+/ ^B.fk0         );PHPChina 开源社区门户y7G1n1FS
     }    //end function

     function open($save_path, $session_name) {
9z*S!nbGg0         return true;PHPChina 开源社区门户E9GtFy{+E
     }    //end function

     function close() {
;n }9N"j @/ZyQ(@U#f2N0         global $MY_SESS_CONN;

         if ($MY_SESS_CONN) {     //关闭数据库连接PHPChina 开源社区门户5R TM qVj(jA
             $MY_SESS_CONN->Close();PHPChina 开源社区门户(M&B#Q9v"g%L
         }PHPChina 开源社区门户(h*C)U@-BN4zQ&u
         return true;
P/e$H+K EY0     }    //end function

     function read($sesskey) {
!X]2lp6F5S0         global $MY_SESS_CONN;

         $sql = ‘SELECT data FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey) . ‘ AND expiry>=’ . time();PHPChina 开源社区门户&[)^^M-v ~o
         $rs =& $MY_SESS_CONN->Execute($sql);
:TR.Zm u:M0         if ($rs) {PHPChina 开源社区门户!R^1E k^y9G3f^
             if ($rs->EOF) {
? si9})YQ0                 return ‘’;
:v~@ GZl-@9~t5V0             } else {     //读取到对应于 SESSION ID 的 SESSION 数据PHPChina 开源社区门户$~4NXnrw K
                 $v = $rs->fields[0];
H I7N-q!i0m9aG0                 $rs->Close();PHPChina 开源社区门户`2p.Dn(To
                 return $v;PHPChina 开源社区门户 V~*kU(_
             }    //end ifPHPChina 开源社区门户1kXyh4mR
         }    //end ifPHPChina 开源社区门户;y(~M2?I&I#y%z-v$?i
         return ‘’;
e/K+@X4c0     }    //end function

     function write($sesskey, $data) {PHPChina 开源社区门户&px4v /;@SK
         global $MY_SESS_CONN;
h?h2c2Wa0       
4In2K,MZ&a7T:OfHb0         $qkey = $MY_SESS_CONN->qstr($sesskey);PHPChina 开源社区门户2U@:n6p"uy c#u
         $expiry = time() + My_SESS_TIME;     //设置过期时间
fw"aD;dc}-i(RlO0       
ZT-HHr8w @_E$GtZ0         //写入 SESSION
3YCc|%U"O6I^8vv0         $arr = array(PHPChina 开源社区门户J0V}a0I.SC/IT J
             ’sesskey’ => $qkey,PHPChina 开源社区门户LXn-V/}(o"u"W
             ‘expiry’   => $expiry,
Dn2[mHQ2}m0             ‘data’     => $data);
.k?h!a0pNE0         $MY_SESS_CONN->Replace(’sess’, $arr, ’sesskey’, $autoQuote = true);
'uS~*a){d$FP0         return true;
vFa3? G }}0     }    //end function

     function destroy($sesskey) {PHPChina 开源社区门户E%s Rvu M"xB
         global $MY_SESS_CONN;

         $sql = ‘DELETE FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey);
']q[j,|7g.I0         $rs =& $MY_SESS_CONN->Execute($sql);
S*LP J h&D A0         return true;PHPChina 开源社区门户Km @#P't$D!slc
     }    //end function

     function gc($maxlifetime = null) {
.@'H$US4vM}f0         global $MY_SESS_CONN;

         $sql = ‘DELETE FROM sess WHERE expiry. time();PHPChina 开源社区门户Ax.| g/?0jsH
         $MY_SESS_CONN->Execute($sql);
`$z$wM3T5G0         //由于经常性的对表 sess 做删除操作,容易产生碎片,PHPChina 开源社区门户{D&]/tf2|zG
         //所以在垃圾回收中对该表进行优化操作。
q0y6R-~%w6].qM0         $sql = ‘OPTIMIZE TABLE sess’;PHPChina 开源社区门户C d8w&puy
         $MY_SESS_CONN->Execute($sql);
z+[)z8U:`b t!q0         return true;
#e(T:t e+H0     }    //end functionPHPChina 开源社区门户 D(pb{vQS}t
}    ///:~

//使用 ADOdb 作为数据库抽象层。PHPChina 开源社区门户WW"_ENz(POQ
require_once(‘adodb/adodb.inc.php’);PHPChina 开源社区门户(G+B9Y2t7xn ]"w
//数据库配置项,可放入配置文件中(如:config.inc.php)。PHPChina 开源社区门户7fi.L7{|-M;s
$db_type = ‘mysql’;PHPChina 开源社区门户L9WM]6qU:p
$db_host = ‘192.168.212.1′;
3^A$FI0~#/0$db_user = ’sess_user’;PHPChina 开源社区门户3e(S1?J|'E0lI
$db_pass = ’sess_pass’;PHPChina 开源社区门户 ~$Bj I2~6SV,Y7r
$db_name = ’sess_db’;
)W1jpgL2p0[2Z$L3k/0//创建数据库连接,这是一个全局变量。PHPChina 开源社区门户0[0P@9^j
$GLOBALS[‘MY_SESS_CONN’] =& ADONewConnection($db_type);
f,S ~/AI:JJ0$GLOBALS[‘MY_SESS_CONN’]->Connect( $db_host, $db_user, $db_pass, $db_name);PHPChina 开源社区门户s;@6e#O$T
//初始化 SESSION 设置,必须在 session_start() 之前运行!!
q6GQU_//O8?!D0My_Sess::init();

基本的原理差不多就这样,细节方面就看还有很多需要琢磨的地方..