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

来源:互联网 发布:神机妙算软件免费版 编辑:程序博客网 时间:2024/04/28 20:35

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


PHPChina 开源社区门户�/-//kU+CW2i$N
问题起源:想做一个中型的网站,因为势单力薄。部分模块采用整合其它开源系统的方案,比如BBS系统和BLOG系统。首先面临的就是用户身份认证的方式。由于这些不是自己开发的系统,都分别有自己的用户系统,于是面临统一身份认证的过程。
!]w-pw!u C(~.j}^0
#I!p3r*x:A5g/D @|^0以 前看过企业级的Web service方案,主要是通过XML,SOAP,WSDL和UDDI来实现。将应用服务都注册到UDDI服务器中,通过SOAP协议使用XML传递信息 (当然需经过加密)。由于涉及到很多服务部署的问题,用JAVA来做这样的项目肯定是再好不过的了。我的目的只是几个WEB系统的整合,肯定是要排除这么 伟大的方案了。关于Web service有兴趣的朋友可以参考机械工业出版社出版的《Web Servides原理与研发实践》,里面有详细的介绍。
)?4RH0}%}1A0
JI6_~ N.x(cK0那 么对于这样的 小WEB系统的整合该怎么来实现?假设这是个从零开始的项目,除了自己开发的系统外,还要用到一些其它组织开发的开源系统:比如BLOG,CMS, BBS。这些系统都有各自的用户系统。要把他们整合到一块有个原则,就是尽量不要破坏或者修改这些系统。那么要实现统一身份认证,我们必须要有一个用户信 息库,然后吧这个数据库的信息映射到那些子系统的数据库中,在大型项目中,一般都会独立出一台单独的用户信息服务器,大部分高校采用LOAD来存放用户信 息,因为采用的是树状结构,对经常读取但很少修改的数据而言,它的性能是很高的。PHPChina 开源社区门户 [ErHL;s!CO$/

}?6aFL i.X0LOAD用户库和 各子系统用户库的映射有很多种方法,我这里只用最简单的直接映射,也就是帐号和密码都是相同的。PHPChina 开源社区门户7i5/)OG/`cs

!fQ ZHn+}0假设我的域名部署如下PHPChina 开源社区门户xg]b t0Lo�ou }
PHPChina 开源社区门户1~P'Y/OC z d&r5P
http://news.domain.com 这是CMS系统的域名PHPChina 开源社区门户uu|1};~
PHPChina 开源社区门户Ic0]3G+X
http://bbs.domain.com 这是论坛的域名
k!X;D4Br y0PHPChina 开源社区门户d$Y/kw+{#i#W
http://blog.domain.com 这是博客域名PHPChina 开源社区门户u!R0z Riz D8Lzs

]o9T1R�{�y0
http://reg.domain.com 这是统一注册和登录页面的域名
}{'bt~1~Ul$J'y [0首先是注册,我们让所有子系统注册页面都转向到一个注册页面上来(各种脚本语言都有转向函数),比如说当用户希望在
http://blog.domain.com/reg.php注册是,reg.php把这个请求转向到http://reg.domain.com/reg.php.
4p"_ Z"AyShMZ0
K'U8Wd g9xV0在实现注册时,由于刚开始时候子系统并不多,注册时把用户注册信息写入主用户数据库的同时写入各子系统的用户库。以后若有新的子系统加入进来时可以通过帐号激活的方式来实现新系统的帐号激活。
!aQxpHs0
)/aO0L_@)qE0c0用户登录的过程,可以参考如下来自IBM的图片
;n)v'I6L#f9I(U0
Q1kW0O y$i0
y s3R}[%RXFd8Y0
Cv;v Y L0流程描述如下:(仅描述正常流程)
Z1R!UnM B]0
?Z7W7A"edU�q4j{01. 用户使用在统一认证服务注册的用户名和密码(也可能是其他的授权信息,比如数字签名等)登陆统一认证服务;
V3E3N)d?x0
/Z(p;y4f6l"u]"Y|j02. 统一认证服务创建了一个会话,同时将与该会话关联的访问认证令牌返回给用户;
"/x#p8i)lO;Mp`0PHPChina 开源社区门户,c3M,e7@7n| y0a@%y
3. 用户使用这个访问认证令牌访问某个支持统一身份认证服务的应用系统;PHPChina 开源社区门户{@3k4~J SC)F
PHPChina 开源社区门户Q!z;pD3El/@d,s
4. 该应用系统将访问认证令牌传入统一身份认证服务,认证访问认证令牌的有效性;PHPChina 开源社区门户 p EM?R aCk

Tv&x.^;k05. 统一身份认证服务确认认证令牌的有效性;
B DStd2eD0
Pl)T8cW)P�@7i06. 应用系统接收访问,并返回访问结果,如果需要提高访问效率的话,应用系统可选择返回其自身的认证令牌已使得用户之后可以使用这个私有令牌持续访问。PHPChina 开源社区门户)u3hy g4O Xp;~ j

"}5E}$vuG W4~0上面所说的令牌我在WEB引用中可以用COOKIE或者SEESION来实现。
A2U)n]&l0PHPChina 开源社区门户2y!kHW1t LKS
例如通过COOKIE来实现PHPChina 开源社区门户1xO.e;h'U/F#G0TZ
PHPChina 开源社区门户'DEir/@)DT4nG6Mr
1.用户在统一登录页登陆,通过查询主用户数据库判断用户是否合法,若是,则注册该用户的唯一COOKIE标识(可以通过加密用户名和密码得到,网上有很多算法)。
{"fJJddwD/d*A ^0PHPChina 开源社区门户/AMo3`6Ass6u
2.用户进入某子系统时,先判断COOKIE是否注册,若注册了 ,则解密该COOKIE得到用户名和帐号,并判断合法性。PHPChina 开源社区门户.v i*xj7V.~;BfN
PHPChina 开源社区门户DP/@q&?*MhQ
3.如果合法,则立刻在该子系统中注册(可在原子系统的登录脚本种抽出登录的部分做成一个函数)PHPChina 开源社区门户A5A.vo(Jhm;m9D/L
PHPChina 开源社区门户9`q/VZs6] DI,N A
使用COOKIE的优点就是简单,只要设置一下就可以实现COOKIE的跨子域传递PHPChina 开源社区门户[ x!_3]h },{/

$@?$G;e&d4Yli0例如PHPChina 开源社区门户o Y{;bR

8^J&f*g f0setcookie(NC_USER_COOKIE, 用户名, 失效时间, 作用路径, '.domain.com');
s_Dr _,hK0PHPChina 开源社区门户V dq a#Yk"M
就能实现在所有.domain.com子域下的传递。
(E#i8Vs/Nv u0
7vs6N%Z7nP%{0但COOKIE也有他的缺点,首先就是安全级别不高,要提防COOKIE劫持的威胁,其次就是它只能跨子域传递,而不能跨完全不同的域。比如说domain.com和fanghei.com之间就不能传递。
"OO#Dm1@0
^p^r m#W.Q$x0然后再说下SESSION的方式,由于SESSION是存储在服务器端的,所以安全级别肯定要比COOKIE的级别高。但是由于SEESION存储的位置不同,造成了无法跨域传递 。可以通过把SEESION村入数据库来解决这个问题。
$WXw l d'Vj)v0PHPChina 开源社区门户U:q3zIs
由于PHP的SESSION需要用到标识SESSION的COOKIE,所以需要设置下COOKIE的作用域
jBWb+wBJ0
~0eC d4F.i.ln0<?
3b8L? }UT0PHPChina 开源社区门户Yg%}Y.r8l%X
ini_set(’session.cookie_domain’, ‘.domian.com’);PHPChina 开源社区门户3L-o;G ^m;b

PI _w2|2eut,_0?>

PHPChina 开源社区门户 G1Q4yD-O'b7l(j3u

然后重写PHP的SESSION操作函数。
P}HD-S"y*R O,CN%G0PHPChina 开源社区门户Wg7l.o.DHxo�O
PHP 提供了session_set_save_handle() 函数来自定义 SESSION 的处理过程,先将 session.save_handler 改成 user
{[PH7n&{aYPeX0PHPChina 开源社区门户I`-y,bo8d5Q
session_module_name(‘user’);
ar!S1E8F /2x0PHPChina 开源社区门户FO&J#rD!zu
然后几可以重写SESSION的操作了,下面是PHP牛人NIO写的SESSION操作
,pk['S.q(F0[php]PHPChina 开源社区门户M T"c6F+Ew
<?phpPHPChina 开源社区门户#H/@Q&i_S*njH
define(‘MY_SESS_TIME’, 3600);   //SESSION 生存时长PHPChina 开源社区门户AI`yA"N
//类定义PHPChina 开源社区门户#}2}d T-i5`0^
class My_Sess
~N5~E y6UP7i8D0{PHPChina 开源社区门户"[rho5~$iq{?l/t
  function init()
2O$SL [%tl6CMm0  {
:R4p&v5|cQ;Ef0    $domain = ‘.infor96.com’;PHPChina 开源社区门户6v W2F|IA6ko
    //不使用 GET/POST 变量方式PHPChina 开源社区门户vv P wm%yc
    ini_set(’session.use_trans_sid’,   0);
6y$St&[:MB WX;a a0    //设置垃圾回收最大生存时间
GP&tM / Q I @0    ini_set(’session.gc_maxlifetime’,   MY_SESS_TIME);
MKa;K7k8d4M.`5q0
o(JU sP5[`0    //使用 COOKIE 保存 SESSION ID 的方式PHPChina 开源社区门户ThIf(q cE
    ini_set(’session.use_cookies’,     1);PHPChina 开源社区门户`BZCq3n3{"TN
    ini_set(’session.cookie_path’,     ‘/’);
.~"u)Rc�M0    //多主机共享保存 SESSION ID 的 COOKIE
;W^2/&MDq0    ini_set(’session.cookie_domain’,   $domain);PHPChina 开源社区门户 YO'h:L~Q]0?

6bZ7_8gOD0    //将 session.save_handler 设置为 user,而不是默认的 files
&[zgy4dm3LCX0    session_module_name(‘user’);
_j}4iS0    //定义 SESSION 各项操作所对应的方法名:PHPChina 开源社区门户I/^2dS
    session_set_save_handler(PHPChina 开源社区门户F:}:aqo&i'om3{
        array(‘My_Sess’, ‘open’),   //对应于静态方法 My_Sess::open(),下同。
$a[)_-oLv&b5QC0        array(‘My_Sess’, ‘close’),PHPChina 开源社区门户+rFXGJBV
        array(‘My_Sess’, ‘read’),
:}*`9fhYl0        array(‘My_Sess’, ‘write’),PHPChina 开源社区门户 cTzN-y I|
        array(‘My_Sess’, ‘destroy’),
E-@xFf)i5[`0        array(‘My_Sess’, ‘gc’)
2]jy#}9b ro-| E6C0    );PHPChina 开源社区门户`;h~NV5^voX"~W
  }   //end functionPHPChina 开源社区门户_]il0B e0a9mm+` Hm

l!Xm2Z a;N-W0  function open($save_path, $session_name) {PHPChina 开源社区门户 gT-GCa%q$gE
    return true;PHPChina 开源社区门户OJ7}2w3C(zl
  }   //end functionPHPChina 开源社区门户&P&MB2Uc f*}T

7j.[%Aspj)^ALi0  function close() {
s9a9` p@o ^H(CGQ0    global $MY_SESS_CONN;PHPChina 开源社区门户2FC_.~ qFy1/F

_V&Y|-tSE0    if ($MY_SESS_CONN) {   //关闭数据库连接PHPChina 开源社区门户8C'f5TlQ(uQ-P
        $MY_SESS_CONN->Close();
k:Zb3rn} `?0    }PHPChina 开源社区门户QB LwJ%gB kc
    return true;
2xN.Xvg"qqZ0  }   //end functionPHPChina 开源社区门户$e(k_ O%c}-F

^K QX j;vn0  function read($sesskey) {PHPChina 开源社区门户&@i6O |Bo
    global $MY_SESS_CONN;PHPChina 开源社区门户~mcTjfLWW
PHPChina 开源社区门户T7r6t�cFa
    $sql = ‘SELECT data FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey) . ‘ AND expiry>=’ . time();
h /COy6V0    $rs =& $MY_SESS_CONN->Execute($sql);PHPChina 开源社区门户"Y zrNA:gq p
    if ($rs) {PHPChina 开源社区门户h DJo| E
        if ($rs->EOF) {
7w'FG!AO*i0          return ‘’;PHPChina 开源社区门户oKE;V)f/]X$Q
        } else {   //读取到对应于 SESSION ID 的 SESSION 数据
W c z`J{0          $v = $rs->fields[0];
Y:pS"e,/0          $rs->Close();
/C,L1p9?@0          return $v;PHPChina 开源社区门户c%aXQD4p&Z3U
        }   //end if
6RS/A6p*s4xL0    }   //end if
'v Ph:g!}6lw.~/h0    return ‘’;
WD)c{ RR0  }   //end function
Tc&xU@q0
,^y8Z@-_#TZ0  function write($sesskey, $data) {PHPChina 开源社区门户/K V Pi�R
    global $MY_SESS_CONN;PHPChina 开源社区门户 Y4C:w)W%Z^
    PHPChina 开源社区门户qmlC8S jl"o
    $qkey = $MY_SESS_CONN->qstr($sesskey);PHPChina 开源社区门户7F8^;_{ o6cr?5R
    $expiry = time() + My_SESS_TIME;   //设置过期时间
(LFbv2Lxu oy7K�f0    PHPChina 开源社区门户eD1@ b9~c$T*F0B#Z$dT
    //写入 SESSION
O [ym2E/dfEc$V0    $arr = array(
eE1YK S0        ’sesskey’ => $qkey,
~ UOvGM3M _6j;re0        ‘expiry’ => $expiry,PHPChina 开源社区门户6D"W%J3RT6uB
        ‘data’   => $data);PHPChina 开源社区门户X3Z g9D9n%I5}N
    $MY_SESS_CONN->Replace(’sess’, $arr, ’sesskey’, $autoQuote = true);
d OL7?yRt0    return true;
M*R0W(lILE i'y6D6U0  }   //end function
7Ei0S Y&/xg0PHPChina 开源社区门户2nP"lLe kL)q
  function destroy($sesskey) {
]6H E Ib0    global $MY_SESS_CONN;PHPChina 开源社区门户8b/Q#]BM

gTX6I mJ2~fh0    $sql = ‘DELETE FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey);PHPChina 开源社区门户b%e |@w'N0t8T
    $rs =& $MY_SESS_CONN->Execute($sql);PHPChina 开源社区门户5v M| j cj,p J0}
    return true;
s#/tjV'p+d u&B0  }   //end functionPHPChina 开源社区门户#H~N/~c ~
PHPChina 开源社区门户Z1xk S*gv&X}
  function gc($maxlifetime = null) {PHPChina 开源社区门户S#jBU(?1c7^
    global $MY_SESS_CONN;
A8O t(W8UC$[9G-|0
'r2Z;M$I/A!`O*q(cH0    $sql = ‘DELETE FROM sess WHERE expiry<’ . time();
Q8X](Olp(?AU3vP0    $MY_SESS_CONN->Execute($sql);
W9e8e rC7Gp._:yzD0    //由于经常性的对表 sess 做删除操作,容易产生碎片,PHPChina 开源社区门户sL ?8Q3Q,r6i
    //所以在垃圾回收中对该表进行优化操作。PHPChina 开源社区门户 O1N pH,Tc|O.w7@&r
    $sql = ‘OPTIMIZE TABLE sess’;
e r)k&n WAUJb-x0    $MY_SESS_CONN->Execute($sql);
5@O2Xi'j9dv2e,O$L0    return true;PHPChina 开源社区门户/y0A#q7f2_:IF
  }   //end function
.} b(Z'~ FL? wF0}   ///:~PHPChina 开源社区门户lD/A#g1[5?�w}D4d

!{.KA^Z0//使用 ADOdb 作为数据库抽象层。
e3G&v0vu zuVC0require_once(‘adodb/adodb.inc.php’);
4[(d{5[/*O#ub0//数据库配置项,可放入配置文件中(如:config.inc.php)。PHPChina 开源社区门户,k `cp.Tb-i!A
$db_type = ‘mysql’;PHPChina 开源社区门户BtGc7V h T
$db_host = ‘192.168.212.1′;
)c_w/d.[:_V0$db_user = ’sess_user’;PHPChina 开源社区门户9z0d+eF EW1m-v^
$db_pass = ’sess_pass’;
({/8F+lo0$db_name = ’sess_db’;
C8cu| B0//创建数据库连接,这是一个全局变量。PHPChina 开源社区门户w7e1Sj ?!J(N
$GLOBALS[‘MY_SESS_CONN’] =& ADONewConnection($db_type);
#f`[!q3O.v7K |r3a0$GLOBALS[‘MY_SESS_CONN’]->Connect( $db_host, $db_user, $db_pass, $db_name);
.R-|j~` A%s0//初始化 SESSION 设置,必须在 session_start() 之前运行!!PHPChina 开源社区门户Q G']G,U3n3m�L6rR
My_Sess::init();PHPChina 开源社区门户7d-_ xf*u-J
?>
X;T%i1iQ,j~0[/php]PHPChina 开源社区门户M#]4z�SjOr)M#Z
PHPChina 开源社区门户'n1Vp)P H/s
基本的原理差不多就这样,细节方面就看还有很多需要琢磨的地方.PHPChina 开源社区门户uYN']BY_

(WC9y,c7FxqK"c m0看了下家园网的通行证件实现,我猜应该是通过SESSION来实现的吧
f6L7U ]}1P�f+O |R0
v/:o-uZ0登录页面为:
http://reg.ncuhome.com/reg.aspPHPChina 开源社区门户3m/] |xd
PHPChina 开源社区门户qh `;H/)X2jF
登录成功后转到:http://reg.ncuhome.com/Cindex.asp
GeTP e9W/N�s/ec0PHPChina 开源社区门户 |SF{5~T
然后通过向http://reg.ncuhome.com/gonewpage.asp?url=这个页面传递子系统参数来分别登陆子系统.PHPChina 开源社区门户h2j~G1U7P-b q

]-H Y ` _A0登陆了这么多次发现这样做存在一个问题,那就是如果如果想要登陆任何一个子系统都要重新返回
http://reg.ncuhome.com/Cindex.asp来登陆.PHPChina 开源社区门户aoU3A5L%aS C
PHPChina 开源社区门户o9w~B&G x Fq
当我通过
http://reg.ncuhome.com/Cindex.asp进入了BLOG系统后虽然显示我登陆了博客,如果我在浏览器里输入http://bbs.ncuhome.com时候,论坛依然显示我没登陆.而要跑Cindex.asp去点社区进入论坛才能登陆.感觉这有点违背了单点登陆,全站通行的目标了.希望家园网能把这点完善下.

 
原创粉丝点击