PHPChina 开源社区门户-qgg`C~9G s9d1_/^

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

_#z/I||{%^0PHPChina 开源社区门户dM'[1q$f'A$t Y+@

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

7ae)CO8i*p0PHPChina 开源社区门户)c,a^Ipr;v

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

we5L_9C%SVm0PHPChina 开源社区门户&B l1ks)nq4t

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

PHPChina 开源社区门户ud y0Y)lJd

假设我的域名部署如下

M;|C(? nV!b2W0

d]OP3D^ F0http://news.domain.com 这是CMS系统的域名

5_"D9O6\ ~8s2Qu0PHPChina 开源社区门户+VR's7wK!M

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

Xm)pE-V1G0

u'ZfiT+o0http://blog.domain.com 这是博客域名PHPChina 开源社区门户2ZC M&j/P6S

PHPChina 开源社区门户!e*oi._n+`O9ca#p

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

KmZ7Wa [0

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

7Q i u,l~0

!e4v]XZM@W0用户登录的过程,可以参考如下来自IBM的图片

R(N$j$Z M#D0

,Iu/IQ F0

8l ]_\UK.yag0PHPChina 开源社区门户~r3Y$X#Uwg

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

#u"N Lhm,S+T0

v9lv.O'{0PHPChina 开源社区门户'IO]1lF4~

PHPChina 开源社区门户`4Vtm&yh3U m5O

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

au/[ c9c0

l^F#M#[PRQ6J!m7Zc01. 用户使用在统一认证服务注册的用户名和密码(也可能是其他的授权信息,比如数字签名等)登陆统一认证服务;PHPChina 开源社区门户;\'O a XI%g

PHPChina 开源社区门户!db8~ R(M

2. 统一认证服务创建了一个会话,同时将与该会话关联的访问认证令牌返回给用户;PHPChina 开源社区门户+Y g5nR1T9zBq

PHPChina 开源社区门户 qg~j5cX9A c

3. 用户使用这个访问认证令牌访问某个支持统一身份认证服务的应用系统;PHPChina 开源社区门户Mu0c8],F$]

PHPChina 开源社区门户/V h%Jx^

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

Sc n'v3^-EDq0

K/d4ar9L"ho05. 统一身份认证服务确认认证令牌的有效性;

ay:iEq0

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

ClF)y}w5qU0

V V~:^b%C)N0上面所说的令牌我在WEB引用中可以用 COOKIE或者SEESION来实现。

*xN;q+nU[#L,L/@0PHPChina 开源社区门户0kXF!Y7BOC$u{7^

例如通过COOKIE来实现

[b^ h*d*n0PHPChina 开源社区门户 p2b l\ w

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

tV,A+B_0

Ij H A|M02.用户进入某子系统时,先判断COOKIE是否注册,若注册了,则解密 该COOKIE得到用户名和帐号,并判断合法性。PHPChina 开源社区门户M EqISQ|I;{

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

I6F"L,{P|4i$OIU]0

|r ?V0Sz#Lh0使用COOKIE的优点就是简单,只要设置一下就可以实现 COOKIE的跨子域传递PHPChina 开源社区门户o`R1r+B _

PHPChina 开源社区门户7QJ#@7q/_7k8jW"x

例如

Y5Q;s8`\e*G)R-[ w0

&F!p ZElEu0setcookie(NC_USER_COOKIE, 用户名, 失效时间, 作用路径, ‘.domain.com’);

&Oi(d/`D!B/j{0

]$X)WL/n mE8l0就能实现在所有.domain.com子域下的传递。

k DE2Z:Ql)Z oA%I0PHPChina 开源社区门户:D N5a'T8vnF

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

0N dA1@o0PHPChina 开源社区门户b?DV.PjZ Qh

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

PHPChina 开源社区门户,Z:^M"?&?T,{E.u"L*o"td

由 于PHP的SESSION需要用到标识SESSION的 COOKIE,所以需要设置下COOKIE的作用域PHPChina 开源社区门户!_I*Dg"f.V7qR

Pv;oZ M)CyTc0ini_set(’session.cookie_domain’, ‘.domian.com’);

/SMB0K]2L7z:oCa0

[-h"bKX4N9W i_3t Qs0然后重写PHP的SESSION操作函数。

L%}x*{c0he$uB9]S0PHPChina 开源社区门户F+ef#LH;r

PHP 提供了session_set_save_handle() 函数来自定义 SESSION 的处理过程,先将 session.save_handler 改成 userPHPChina 开源社区门户E(T0w5z%e&pr`a#t

PHPChina 开源社区门户8y~Y^uRV7O'hL

session_module_name(‘user’);

f0R%S x1?K m.J$Z0PHPChina 开源社区门户 huXH9C9Wy5o1p

然后几可以重写SESSION的操作了,下面是PHP牛人NIO写的SESSION操作PHPChina 开源社区门户.EsV$R^aG

PHPChina 开源社区门户%_7\1MA!A?&m/q,j

define(‘MY_SESS_TIME’, 3600);    //SESSION 生存时长
T,^s]w{0//类定义
pn;`4P'Z^9Q K's9V0class My_SessPHPChina 开源社区门户N A:F(y;p
{
cB]p#q+G8`0     function init()
\L@z$|"lkG0     {PHPChina 开源社区门户WM Z4qwr(QH ay
         $domain = ‘.infor96.com’;
#?z`U'sdJ3F2J_0         //不使用 GET/POST 变量方式
nl&K&i3P0         ini_set(’session.use_trans_sid’,     0);PHPChina 开源社区门户6Z!T X ~7Y O%V"Z \
         //设置垃圾回收最大生存时间
{0PoHLD^\p0         ini_set(’session.gc_maxlifetime’,    MY_SESS_TIME);

9O8@o"Y cU!Zt0PHPChina 开源社区门户8B b [_ vn3mho%V"v

         //使用 COOKIE 保存 SESSION ID 的方式PHPChina 开源社区门户5z.p.ETg
         ini_set(’session.use_cookies’,       1);
H5Cow1\7g0         ini_set(’session.cookie_path’,       ‘/’);
*eRFKNX0         //多主机共享保存 SESSION ID 的 COOKIEPHPChina 开源社区门户y-]j8X6^p*Ty
         ini_set(’session.cookie_domain’,     $domain);

#D;|.GwS |R y0PHPChina 开源社区门户0dQ({UwR#s{4a

         //将 session.save_handler 设置为 user,而不是默认的 filesPHPChina 开源社区门户r0Q4j0fNn
         session_module_name(‘user’);
`-d}:_U/E X U_k0         //定义 SESSION 各项操作所对应的方法名:
xDo*D+J;u_u&R6v0         session_set_save_handler(
?ap&K B1M%{5t[0             array(‘My_Sess’, ‘open’),    //对应于静态方法 My_Sess::open(),下同。PHPChina 开源社区门户SS-?{ ktP
             array(‘My_Sess’, ‘close’),
6G7p5S-F C/g?]&^j5S0             array(‘My_Sess’, ‘read’),PHPChina 开源社区门户$df ^ak/j'x:Q}4K"k
             array(‘My_Sess’, ‘write’),
_){9KY!V(Xp }&Ir0             array(‘My_Sess’, ‘destroy’),
!pW4rPNT+x${0             array(‘My_Sess’, ‘gc’)PHPChina 开源社区门户 ~`M,@D
         );PHPChina 开源社区门户7oA!XtB{(U&} XN
     }    //end function
PHPChina 开源社区门户^pg$V.g9\ e+Z

)Y2eb/p/S8X0     function open($save_path, $session_name) {
{.L!kw:tm Ar0         return true;
3Jo6z#A_7Ckq0     }    //end function
PHPChina 开源社区门户f A!a3Z"SK,N7L6Q

PHPChina 开源社区门户;t EEM1Cs~Pe*A

     function close() {
\5])ABf?0         global $MY_SESS_CONN;

v }rJst,kM0PHPChina 开源社区门户1sQp$R-xE

         if ($MY_SESS_CONN) {     //关闭数据库连接
Vf'_%} W0             $MY_SESS_CONN->Close();PHPChina 开源社区门户Z/Q W0Y8e;k#g1a6ly
         }PHPChina 开源社区门户:te#R8UR6]m m[
         return true;
n*y&]0B a0gx c0     }    //end function

2x"A7b3K+l J]b&h0PHPChina 开源社区门户-c&t l`KW$z#k8k_y

     function read($sesskey) {
g}U)\.qb0         global $MY_SESS_CONN;

.U|WD7@h0PHPChina 开源社区门户S/NY/W @

         $sql = ‘SELECT data FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey) . ‘ AND expiry>=’ . time();PHPChina 开源社区门户FpDG ^s:L8p$~@"b
         $rs =& $MY_SESS_CONN->Execute($sql);
T;j}/M-t+S5j0         if ($rs) {
'h(I^5eJ"N M$Hb)o:gx0             if ($rs->EOF) {PHPChina 开源社区门户H#tw%l#N4Z0o)u
                 return ‘’;
N|S ~/J:VT0             } else {     //读取到对应于 SESSION ID 的 SESSION 数据
t(C~){rkE0                 $v = $rs->fields[0];PHPChina 开源社区门户"y1Z UX'NX
                 $rs->Close();
,~aH4C*j E/CC0                 return $v;PHPChina 开源社区门户 sm*{wP"hB
             }    //end ifPHPChina 开源社区门户{xe9c/z5I
         }    //end ifPHPChina 开源社区门户HRQp'P WN
         return ‘’;PHPChina 开源社区门户/NJ#ufMB3O.wn
     }    //end function

5Jp}tE.b0PHPChina 开源社区门户:w$n&z Y^1m

     function write($sesskey, $data) {
$Ujfp}l(n9Et7| l0         global $MY_SESS_CONN;PHPChina 开源社区门户 a;S)n#X+o2f@;@.q i
       
a,e!I9Ej}0         $qkey = $MY_SESS_CONN->qstr($sesskey);PHPChina 开源社区门户\?X]:dc$u*}.ky
         $expiry = time() + My_SESS_TIME;     //设置过期时间
dgf#Dc omK} Q0yv0       PHPChina 开源社区门户y)Fzv[R
         //写入 SESSION
l m#dOyCe0         $arr = array(PHPChina 开源社区门户Tgj+q7W(N9k
             ’sesskey’ => $qkey,PHPChina 开源社区门户8l\7rFSM)AOA
             ‘expiry’   => $expiry,
:tAuV|HTXyx0             ‘data’     => $data);
!b(R GWo0         $MY_SESS_CONN->Replace(’sess’, $arr, ’sesskey’, $autoQuote = true);
f"d uEh#m0         return true;
7su*Okj8^V_1c#ZD2H0     }    //end function

j G;c IMZyt0

?Lr FJ0     function destroy($sesskey) {
}6T!@4b.\.v0         global $MY_SESS_CONN;
PHPChina 开源社区门户4XZA\C@pA&O

PHPChina 开源社区门户nj*_j!Zq e0f

         $sql = ‘DELETE FROM sess WHERE sesskey=’ . $MY_SESS_CONN->qstr($sesskey);PHPChina 开源社区门户/E/cN2i:Ws;\
         $rs =& $MY_SESS_CONN->Execute($sql);PHPChina 开源社区门户Nz#FF:eI8f
         return true;
b1z;`yY!Q,L~)|0     }    //end function

c[P"G8L'\`U$\0

F3k$l&[^P4E0     function gc($maxlifetime = null) {
/cf[|%lk I4J0         global $MY_SESS_CONN;
PHPChina 开源社区门户f*]H;?+QC9x"oZ6tRD3F1C

PHPChina 开源社区门户$Y+p i0b8QH}ixA

         $sql = ‘DELETE FROM sess WHERE expiry. time();
P)X x&F6LZ`B4D7m5x0         $MY_SESS_CONN->Execute($sql);PHPChina 开源社区门户R] sxn^
         //由于经常性的对表 sess 做删除操作,容易产生碎片,PHPChina 开源社区门户n$f/nnNb3tA
         //所以在垃圾回收中对该表进行优化操作。
5AT.Q9m["P~qi0         $sql = ‘OPTIMIZE TABLE sess’;
V UO0P2z6gw"C?0         $MY_SESS_CONN->Execute($sql);PHPChina 开源社区门户~1h l*UHe~
         return true;PHPChina 开源社区门户3@8yT_0m7{
     }    //end functionPHPChina 开源社区门户Y1L n+S+Rz(s}9N
}    ///:~

4J QR C erra"n P&X\0

)q,V,L!s$\0//使用 ADOdb 作为数据库抽象层。PHPChina 开源社区门户 stq4@1[MBw
require_once(‘adodb/adodb.inc.php’);PHPChina 开源社区门户*Z @v!J plU!m
//数据库配 置项,可放入配置文件中(如:config.inc.php)。
K d d*N~PCB0$db_type = ‘mysql’;PHPChina 开源社区门户%~3zG!c${viM6vL1R
$db_host = ‘192.168.212.1′;PHPChina 开源社区门户!N1_\%oXT+pD/J
$db_user = ’sess_user’;PHPChina 开源社区门户}2H+l U9q;J z4svtI
$db_pass = ’sess_pass’;
{:|.T t"ORg@0$db_name = ’sess_db’;
?d*w q6Aw*Tc0//创建数据库连接,这是一个全局变量。
+Xb? r't;M0$GLOBALS[‘MY_SESS_CONN’] =& ADONewConnection($db_type);
ic q Z2Mk V0$GLOBALS[‘MY_SESS_CONN’]->Connect( $db_host, $db_user, $db_pass, $db_name);PHPChina 开源社区门户'C{2~5_ ?a3o
//初始化 SESSION 设置,必须在 session_start() 之前运行!!
'~xV#a y,f p7j0My_Sess::init();
PHPChina 开源社区门户,P_wVG'L

PHPChina 开源社区门户"Ws1EjH4NR^.wx基本的原理差不多就这 样,细节方面就看还有很多需要琢磨的地方..

原文:http://www.phpchina.com/html/50/3950-13323.html