??xml version="1.0" encoding="utf-8" standalone="yes"?>
来源:不详 (2006-06-01 16:49:09)
一、术语session
在我的经验里Qsessionq个词被滥用的程度大概仅ơ于transactionQ更加有的是transaction与session在某些语境下的含义是相同的?/p>
sessionQ中文经常翻译ؓ会话Q其本来的含义是指有始有l的一pd动作/消息Q比如打电话时从拿v电话拨号到挂断电话这中间的一pdq程可以UCZ个session?/p>
有时候我们可以看到这L?#8220;在一个浏览器会话期间Q?..”Q这里的会话一词用的就是其本义Q是指从一个浏览器H口打开到关闭这个期间①。最混ؕ的是“用户Q客L
Q在一ơ会话期?#8221;q样一句话Q它可能指用L一pd动作Q一般情况下是同某个具体目的相关的一pd动作Q比如从d到选购商品到结账登样一个网上购物的q程
Q有时候也被称Z个transactionQ,然而有时候也可能仅仅是指一ơ连接,也有可能是指含义①,其中的差别只能靠上下文来推断②?/p>
然而当session一词与|络协议相关联时Q它又往往隐含?#8220;面向q接”??#8220;保持状?#8221;q样两个含义Q?#8220;面向q接”指的是在通信双方在通信之前要先建立一个通信
的渠道,比如打电话,直到Ҏ接了电话通信才能开始,与此相对的是写信Q在你把信发出去的时候你q不能确认对方的地址是否正确Q通信渠道不一定能建立Q但对发信h
来说Q通信已经开始了?#8220;保持状?#8221;则是指通信的一方能够把一pd的消息关联v来,使得消息之间可以互相依赖Q比如一个服务员能够认出再次光的老顾客ƈ且记得上
ơ这个顾客还Ơ店里一块钱。这一cȝ例子?#8220;一个TCP session”或?#8220;一个POP3 session”③?/p>
而到了web服务器蓬勃发展的时代Qsession在web开发语境下的语义又有了新的扩展Q它的含义是指一cȝ来在客户端与服务器之间保持状态的解决Ҏ④。有时?/p>
session也用来指q种解决Ҏ的存储结构,?#8220;把xxx保存在session?#8221;⑤。由于各U用于web开发的语言在一定程度上都提供了对这U解x案的支持Q所以在某种特定?/p>
a的语境下Qsession也被用来指代该语a的解x案,比如l常把Java里提供的javax.servlet.http.HttpSessionUCؓsession⑥?/p>
鉴于q种混ؕ已不可改变,本文中session一词的q用也会Ҏ上下文有不同的含义,请大家注意分辨?/p>
在本文中Q用中?#8220;览器会话期?#8221;来表辑义①Q?#8220;session机制”来表辑义④Q?#8220;session”表达含义⑤,使用具体?#8220;HttpSession”来表辑义⑥
二、HTTP协议与状态保?/p>
HTTP协议本n是无状态的Q这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器h下蝲某些文gQ无论是客户端还是服务器都没有必要纪录彼此过ȝ?/p>
为,每一ơ请求之间都是独立的Q好比一个顾客和一个自动售货机或者一个普通的Q非会员Ӟ大卖Z间的关系一栗?/p>
然而聪明(或者贪心?Q的Z很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用Q就像给有线电视加上Ҏ功能一栗这U需求一斚wqHTML逐步
d了表单、脚本、DOM{客L行ؓQ另一斚w在服务器端则出现了CGI规范以响应客L的动态请求,作ؓ传输载体的HTTP协议也添加了文g上蝲、cookieq些Ҏ。其?/p>
cookie的作用就是ؓ了解决HTTP协议无状态的~陷所作出的努力。至于后来出现的session机制则是又一U在客户端与服务器之间保持状态的解决Ҏ?/p>
让我们用几个例子来描qC下cookie和session机制之间的区别与联系。笔者曾l常ȝ一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠Q然而一ơ性消?杯咖啡的Z?/p>
乎其微,q时需要某U方式来U录某位֮的消Ҏ量。想象一下其实也无外乎下面的几种ҎQ?/p>
1、该店的店员很厉宻I能记住每位顾客的消费数量Q只要顾客一走进咖啡店,店员q道该怎么对待了。这U做法就是协议本w支持状态?/p>
2、发l顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每ơ消ҎQ如果顾客出C张卡片,则此ơ消费就会与以前或以后的消费相联pv来。这U做?/p>
是在客L保持状态?/p>
3、发l顾客一张会员卡Q除了卡号之外什么信息也不纪录,每次消费Ӟ如果֮出示该卡片,则店员在店里的纪录本上找到这个卡号对应的U录d一些消费信息。这
U做法就是在服务器端保持状态?/p>
׃HTTP协议是无状态的Q而出于种U考虑也不希望使之成ؓ有状态的Q因此,后面两种Ҏ成为现实的选择。具体来说cookie机制采用的是在客L保持状态的Ҏ
Q而session机制采用的是在服务器端保持状态的Ҏ。同时我们也看到Q由于采用服务器端保持状态的Ҏ在客L也需要保存一个标识,所以session机制可能需要借助?/p>
cookie机制来达C存标识的目的Q但实际上它q有其他选择?/p>
三、理解cookie机制
cookie机制的基本原理就如上面的例子一L单,但是q有几个问题需要解冻I“会员?#8221;如何分发Q?#8220;会员?#8221;的内容;以及客户如何使用“会员?#8221;?/p>
正统的cookie分发是通过扩展HTTP协议来实现的Q服务器通过在HTTP的响应头中加上一行特D的指示以提C浏览器按照指示生成相应的cookie。然而纯_的客户端脚本如
JavaScript或者VBScript也可以生成cookie?/p>
而cookie的用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器查所有存储的cookieQ如果某个cookie所声明的作用范围大于等于将要请求的资源所?/p>
的位|,则把该cookie附在h资源的HTTPh头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示Q如果某家分店还发行了自q会员卡,那么q这家店?/p>
时候除了要出示麦当劳的会员卡,q要出示q家店的会员卡?/p>
cookie的内容主要包括:名字Q|q期旉Q\径和域?/p>
其中域可以指定某一个域比如.google.comQ相当于d招牌Q比如宝z公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.comQ可以用
飘柔来做比?/p>
路径是跟在域名后面的URL路径Q比?或?foo{等Q可以用某飘柔专柜做比?/p>
路径与域合在一起就构成了cookie的作用范围?/p>
如果不设|过期时_则表C个cookie的生命期为浏览器会话期间Q只要关闭浏览器H口Qcookie消׃。这U生命期为浏览器会话期的cookie被称Z话cookie?/p>
会话cookie一般不存储在硬盘上而是保存在内存里Q当然这U行为ƈ不是规范规定的。如果设|了q期旉Q浏览器׃把cookie保存到硬盘上Q关闭后再次打开览器,q?/p>
些cookie仍然有效直到过讑֮的过期时间?/p>
存储在硬盘上的cookie可以在不同的览器进E间׃nQ比如两个IEH口。而对于保存在内存里的cookieQ不同的览器有不同的处理方式。对于IEQ在一个打开的窗?/p>
上按Ctrl-NQ或者从文g菜单Q打开的窗口可以与原窗口共享,而用其他方式新开的IEq程则不能共享已l打开的窗口的内存cookieQ对于Mozilla Firefox0.8Q所有的q?/p>
E和标签都可以׃n同样的cookie。一般来说是用javascript的window.open打开的窗口会与原H口׃n内存cookie。浏览器对于会话cookie的这U只认cookie不认人的处理
方式l常l采用session机制的web应用E序开发者造成很大的困扰?/p>
下面是一个goolge讄cookie的响应头的例?/p>
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/;
domain=.google.com
Content-Type: text/html
q是使用HTTPLookq个HTTP Sniffer软g来俘LHTTP通讯U录的一部分
览器在再次讉Kgoolge的资源时自动向外发送cookie
使用Firefox可以很容易的观察现有的cookie的?/p>
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理?/p>
IE也可以设|在接受cookie前询?/p>
q是一个询问接受cookie的对话框?/p>
四、理解session机制
session机制是一U服务器端的机制Q服务器使用一U类g散列表的l构Q也可能是使用散列表)来保存信息?/p>
当程序需要ؓ某个客户端的h创徏一个session的时候,服务器首先检查这个客L的请求里是否已包含了一个session标识 - UCؓsession idQ如果已包含一?/p>
session id则说明以前已lؓ此客L创徏qsessionQ服务器按照session id把这个session索出来用(如果索不刎ͼ可能会新Z个)Q如果客Lh不包?/p>
session idQ则为此客户端创Z个sessionq且生成一个与此session相关联的session idQsession id的值应该是一个既不会重复Q植蝗菀妆徽业焦媛梢苑略斓淖址?/p>
飧鰏ession id被在本ơ响应中q回l客L保存?/p>
保存q个session id的方式可以采用cookieQ这样在交互q程中浏览器可以自动的按照规则把q个标识发挥l服务器。一般这个cookie的名字都是类gSEEESIONIDQ?/p>
。比如weblogic对于web应用E序生成的cookieQJSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764Q它的名字就是JSESSIONID?/p>
׃cookie可以被h为的止Q必L其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一U技术叫做URL重写Q就是把session id直接
附加在URL路径的后面,附加方式也有两种Q一U是作ؓURL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK ... 99zWpBng!-145788764另一U是作ؓ查询?/p>
W串附加在URL后面Q表现Ş式ؓhttp://...../xxx?jsessionid=ByOK ... 99zWpBng!-145788764
q两U方式对于用h说是没有区别的,只是服务器在解析的时候处理的方式不同Q采用第一U方式也有利于把session id的信息和正常E序参数区分开来?/p>
Z在整个交互过E中始终保持状态,必d每个客户端可能请求的路径后面都包含这个session id?/p>
另一U技术叫做表单隐藏字Dc就是服务器会自动修改表单,d一个隐藏字D,以便在表单提交时能够把session id传递回服务器?/p>
q种技术现在已较少应用Q笔者接触过的很古老的iPlanet6(SunONE应用服务器的前n)׃用了q种技术。实际上q种技术可以简单的用对action应用URL重写来代ѝ?/p>
在谈论session机制的时候,常常听到q样一U误?#8220;只要关闭览器,session消׃”。其实可以想象一下会员卡的例子,除非֮d对店家提出销卡,否则店家
l对不会L删除֮的资料。对session来说也是一LQ除非程序通知服务器删除一个sessionQ否则服务器会一直保留,E序一般都是在用户做log off的时候发个指令去
删除session。然而浏览器从来不会d在关闭之前通知服务器它要关闭Q因此服务器Ҏ不会有机会知道浏览器已经关闭Q之所以会有这U错觉,是大部分session机制?/p>
使用会话cookie来保存session idQ而关闭浏览器后这个session id消׃Q再ơ连接服务器时也无法找到原来的session。如果服务器讄的cookie被保存到盘上,?/p>
者用某U手D|写浏览器发出的HTTPh_把原来的session id发送给服务器,则再ơ打开览器仍然能够找到原来的session?/p>
恰恰是由于关闭浏览器不会Dsession被删除,q服务器ؓseesion讄了一个失效时_当距dL上一ơ用session的时间超q这个失效时间时Q服务器可?/p>
认ؓ客户端已l停止了zdQ才会把session删除以节省存储空间?/p>
五、理解javax.servlet.http.HttpSession
HttpSession是Javaq_对session机制的实现规范,因ؓ它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细
微差异。这里我们以BEA的Weblogic Server8.1作ؓ例子来演C?/p>
首先QWeblogic Server提供了一pd的参数来控制它的HttpSession的实玎ͼ包括使用cookie的开关选项Q用URL重写的开关选项Qsession持久化的讄Qsession失效
旉的设|,以及针对cookie的各U设|,比如讄cookie的名字、\径、域Qcookie的生存时间等?/p>
一般情况下Qsession都是存储在内存里Q当服务器进E被停止或者重启的时候,内存里的session也会被清I,如果讄了session的持久化Ҏ,服务器就会把session
保存到硬盘上Q当服务器进E重新启动或q些信息能够被再次使用QWeblogic Server支持的持久性方式包括文件、数据库、客Lcookie保存和复制?/p>
复制严格说来不算持久化保存,因ؓsession实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进E中Q这样即使某个服务器q程停止工作也仍
然可以从其他q程中取得session?/p>
cookie生存旉的设|则会媄响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解?/p>
cookie的\径对于web应用E序来说是一个非帔R要的选项QWeblogic Server对这个选项的默认处理方式得它与其他服务器有明昄区别。后面我们会专题讨论?/p>
关于session的设|参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
六、HttpSession常见问题
Q在本小节中session的含义ؓ⑤和⑥的混合Q?/p>
1、session在何时被创徏
一个常见的误解是以为session在有客户端访问时p创徏Q然而事实是直到某server端程序调用HttpServletRequest.getSession(true)q样的语句时才被创徏Q注意如
果JSP没有昄的?关闭sessionQ则JSP文g在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);q也是JSP?/p>
隐含的session对象的来历?/p>
׃session会消耗内存资源,因此Q如果不打算使用sessionQ应该在所有的JSP中关闭它?/p>
2、session何时被删?/p>
l合前面的讨论,session在下列情况下被删除a.E序调用HttpSession.invalidate();或b.距离上一ơ收到客L发送的session id旉间隔过了session的超时设|?
或c.服务器进E被停止Q非持久sessionQ?/p>
3、如何做到在览器关闭时删除session
严格的讲Q做不到q一炏V可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来
删除session。但是对于浏览器崩溃或者强行杀死进E这些非常规手段仍然无能为力?/p>
4、有个HttpSessionListener是怎么回事
你可以创Llistenerȝ控session的创建和销毁事Ӟ使得在发生这L事g时你可以做一些相应的工作。注意是session的创建和销毁动作触发listenerQ而不
是相反。类似的与HttpSession有关的listenerq有HttpSessionBindingListenerQHttpSessionActivationListener和HttpSessionAttributeListener?
5、存攑֜session中的对象必须是可序列化的?/p>
不是必需的。要求对象可序列化只是ؓ了session能够在集中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server?/p>
session中放|一个不可序列化的对象在控制C会收C个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象Q在session销毁时会有一个ExceptionQ很
奇怪?/p>
6、如何才能正的应付客户端禁止cookie的可能?/p>
Ҏ有的URL使用URL重写Q包括超链接Qform的actionQ和重定向的URLQ具体做法参见[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
7、开两个览器窗口访问应用程序会使用同一个sessionq是不同的session
参见W三节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器Q不同的H口打开方式以及不同的cookie存储方式都会对这个问题的{案有媄响?/p>
8、如何防止用h开两个览器窗口操作导致的session混ؕ
q个问题与防止表单多ơ提交是cM的,可以通过讄客户端的令牌来解冟뀂就是在服务器每ơ生成一个不同的idq回l客LQ同时保存在session里,客户端提交表?/p>
时必Lq个id也返回服务器Q程序首先比较返回的id与保存在session里的值是否一_如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表C层
模式的部分。需要注意的是对于用javascript window.open打开的窗口,一般不讄q个idQ或者用单独的idQ以防主H口无法操作Q徏议不要再window.open打开的窗?/p>
里做修改操作Q这样就可以不用讄?/p>
9、ؓ什么在Weblogic Server中改变session的值后要重新调用一ơsession.setValue
做这个动作主要是Z在集环境中提示Weblogic Server session中的值发生了改变Q需要向其他服务器进E复制新的session倹{?/p>
10、ؓ什么session不见?/p>
排除session正常失效的因素之外,服务器本w的可能性应该是微乎其微的,虽然W者在iPlanet6SP1加若q补丁的Solaris版本上倒也遇到q;览器插件的可能性次之,
W者也遇到q?721插g造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题?/p>
出现q一问题的大部分原因都是E序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨个问题?/p>
七、跨应用E序的session׃n
常常有这L情况Q一个大目被分割成若干项目开发,Z能够互不q扰Q要求每个小目作ؓ一个单独的web应用E序开发,可是C最后突然发现某几个项目之
间需要共享一些信息,或者想使用session来实现SSO(single sign on)Q在session中保存login的用户信息,最自然的要求是应用E序间能够访问彼此的session?/p>
然而按照Servlet规范Qsession的作用范围应该仅仅限于当前应用程序下Q不同的应用E序之间是不能够互相讉KҎ的session的。各个应用服务器从实际效果上都遵?/p>
了这一规范Q但是实现的l节却可能各有不同,因此解决跨应用程序session׃n的方法也各不相同?/p>
首先来看一下Tomcat是如何实现web应用E序之间session的隔ȝQ从Tomcat讄的cookie路径来看Q它对不同的应用E序讄的cookie路径是不同的Q这样不同的应用
E序所用的session id是不同的Q因此即使在同一个浏览器H口里访问不同的应用E序Q发送给服务器的session id也可以是不同的?/p>
Ҏq个Ҏ,我们可以推测Tomcat中session的内存结构大致如下?/p>
W者以前用q的iPlanet也采用的是同L方式Q估计SunONE与iPlanet之间不会有太大的差别。对于这U方式的服务器,解决的思\很简单,实际实行h也不难。要?/p>
让所有的应用E序׃n一个session idQ要么让应用E序能够获得其他应用E序的session id?/p>
iPlanet中有一U很单的Ҏ来实现共享一个session idQ那是把各个应用程序的cookie路径都设?Q实际上应该?NASAppQ对于应用程序来讲它的作用相当于根)
?/p>
/NASApp
需要注意的是,操作׃n的session应该遵@一些编E约定,比如在session attribute名字的前面加上应用程序的前缀Q得setAttribute("name", "neo")变成
setAttribute("app1.name", "neo")Q以防止命名I间冲突Q导致互相覆盖?/p>
在Tomcat中则没有q么方便的选择。在Tomcat版本3上,我们q可以有一些手D|׃nsession。对于版?以上的TomcatQ目前笔者尚未发现简单的办法。只能借助于第?/p>
方的力量Q比如用文件、数据库、JMS或者客LcookieQURL参数或者隐藏字D늭手段?/p>
我们再看一下Weblogic Server是如何处理session的?/p>
从截屏画面上可以看到Weblogic ServerҎ有的应用E序讄的cookie的\径都?Q这是不是意味着在Weblogic Server中默认的可以共享session了呢Q然而一个小
实验卛_证明即不同的应用程序用的是同一个sessionQ各个应用程序仍然只能访问自己所讄的那些属性。这说明Weblogic Server中的session的内存结构可能如?/p>
对于q样一U结构,在session机制本n上来解决session׃n的问题应该是不可能的了。除了借助于第三方的力量,比如使用文g、数据库、JMS或者客LcookieQURL
参数或者隐藏字D늭手段Q还有一U较为方便的做法Q就是把一个应用程序的session攑ֈServletContext中,q样另外一个应用程序就可以从ServletContext中取得前一个应
用程序的引用。示例代码如下,
应用E序A
context.setAttribute("appA", session);
应用E序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");
值得注意的是q种用法不可ULQ因为根据ServletContext的JavaDocQ应用服务器可以处于安全的原因对于context.getContext("/appA");q回I|以上做法?/p>
Weblogic Server 8.1中通过?/p>
那么Weblogic ServerZ么要把所有的应用E序的cookie路径都设?呢?原来是ؓ了SSOQ凡是共享这个session的应用程序都可以׃n认证的信息。一个简单的实验?/p>
可以证明q一点,修改首先d的那个应用程序的描述Wweblogic.xmlQ把cookie路径修改?appA讉K另外一个应用程序会重新要求dQ即使是反过来,先访问cookie路径
?的应用程序,再访问修改过路径的这个,虽然不再提示dQ但是登录的用户信息也会丢失。注意做q个实验时认证方式应该用FORMQ因为浏览器和web服务器对basic?/p>
证方式有其他的处理方式,W二ơ请求的认证不是通过session来实现的。具体请参看[7] secion 14.8 AuthorizationQ你可以修改所附的CZE序来做q些试验?/p>
八、ȝ
session机制本nq不复杂Q然而其实现和配|上的灵zL却使得具体情况复杂多变。这也要求我们不能把仅仅某一ơ的l验或者某一个浏览器Q服务器的经验当作普遍?/p>
用的l验Q而是始终需要具体情况具体分析?
(http://www.fanqiang.com)
相关文章
旉Q?005-04-20 作者:马晓?/a> 览ơ数Q?158322 本文关键字: |
|
1 J2EE Security ?LDAP Security
2 JAAS和WebLogic Security Framework
3 了解WebLogic LDAP Authentication Provider
4 定制自己的Custom LDAP Authentication Provider
5 部v中的注意事项
6 l束?/a>
7 参考资?/a>
从WebLogic Server 7.0开始,WebLogic Server的安全机制有了全面的改变Q实C一个更加规范的ZJAAS的Security FrameworkQ以及提供了一pd设计良好的Security Service Provider Interface。这h们可以根据自q具体需求,通过Custom Security Authentication Provider来实现安全上的定制功能?/p>
本文以WebLogicQWebLogic Server 8.1Q?Security?LDAP为基Q介lCustom LDAP Authentication Provider如何l我们带来更多的灉|性,和系l安全设计上更多的空_以及讨论如何实现一个Custom LDAP Authentication Provider和部|过E中的一些良好经验?/p>
׃本文涉及到的范围太广Q不可能一一详细讨论Qؓ了没有相关基础的读者也能够阅读理解本文Q因此我在文章前半部分Q试N过最z扼要的描述Q来使大家对于J2EE SecurityQWebLogic Security Framework以及LDAP {有一个初步的清晰认识Q进而可以开发出自己的LDAP Authentication Provider。因此很多地方做了比较有限的描述或者介l,更多详细的内容可以参考文后附带的参考资料或者文中给出的链接?a id="1" name="1">
1 J2EE Security ?LDAP Security
Sun J2EE推出以来Q其安全部分的规范就一直倍受x。我们最常见到安全规范的两个斚w分别是Servlet Security ?EJB Security。目前绝大多数的Servlet容器QJ2EE容器都能很好的支持这些安全规范?/p>
WebLogic Server作ؓ业界领先的J2EE服务器对J2EE Security的支持是非常优秀的。我们这里将l合WebLogic Security和用越来越q泛的LDAP做一个简要的介绍Q这些是设计开发Custom LDAP Authentication Provider的技术基?/p>
1.1 Authentication 和Authorization
q里需要大家先明确安全上的两个重要名词Q一个是认证QAuthenticationQ,一个是授权QAuthorizationQ。认证是回答q个人是谁的问题Q即完成用户名和密码的匹配校验;授权是回{这个h能做什么的问题。我们讨论的J2EE Security包括Declarative Authorization和Programmatic AuthorizationQ即一个是通过web.xmlQejb-jar.xml{部|描q符中的安全声明通过容器提供的服务来完成权限控制的;一个是通过HttpServletRequest.isUserInRole()和EJBContext.isCallerInRole()q样的编E接口在应用中自己完成权限控制的?/p>
1.2 资源QResourceQ和Security Role
资源原本只包?Web Resource和EJB ResourceQ但在WebLogic Security中扩展到几乎M一个WebLogic Platform中的资源Q具体可以参?a target="_blank">http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777。授权就是针对资源的讉K控制?/p>
J2EE Security是基于Security Role的。我们可以将一l资源与一个Security Roleq行兌来达到控制的目的——只有拥有该Role权限的用h能够讉Kq些资源。简单的_我们可以通过l用户分配不同的Security Role来完成权限的控制。复杂的情况下包括用?用户l,以及Principal和Role的映关pȝ{。下面是一个声明性安全在web applicationQwar包中WEB-INF/web.xmlQ中的示例:
<web-app> <security-constraint> <web-resource-collection> <web-resource-name>Success</web-resource-name> <url-pattern>/welcome.jsp</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>webuser</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>default</realm-name> </login-config> <security-role> <role-name>webuser</role-name> </security-role> </web-app> |
只有拥有角色webuser的用h能够讉Kwelcome.jsp面Q否则容器会q回401无权讉K的错误。更多信息请参?a target="_blank">http://e-docs.bea.com/wls/docs81/security/index.html?/p>
同时我们需要在weblogic.xmlQwar包中WEB-INF/weblogic.xmlQ中对security role和principalq行映射关系的配|:
<weblogic-web-app> <security-role-assignment> <role-name>PayrollAdmin</role-name> <principal-name>Tanya</principal-name> </security-role-assignment> </weblogic-web-app> |
q样拥有Principal “Tanya”的用PPrincipal封装到Subject中,用户和Subject兌Q将会拥有PayrollAdmin的权限?/p>
注意Q一般情况下Z化设计,本文中将假设security rolexprincipal nameQ如果不配置security-role-assignmentQWebLogic会默认做此假设)。即上例中Principal-name也ؓPayrollAdmin?/em>
1.3 LDAP Security
LDAP是轻量目录服务QLightweight Directory Access ProtocolQ。越来越多的应用开始采用LDAP作ؓ后端用户存储。在安全上,LDAP Security是基于ACLQAccess Control ListQ的Q它通过l一个用L分配LDAP 操作资源Q比如对一个子树的查询Q修改等Q来最l完成权限的控制。因此在LDAP中,授权工作是以用户lؓ单位q行的。一个用L一般来说是拥有如下一l属性的LDAP EntryQ?/p>
?-3-1
其中objectclass可以为groupOfUniqueNames或者groupOfNamesQ它们对应的l成员属性分别是uniquemember和member。如果是动态组Qobjectclass为groupOfURLs。动态组一般应用在成员可以通过某种业务逻辑q算来决定的情况下。比如,l理为ZHANGSAN的全部员工。下面是一个典型的动态组QmemberURL属性定义了哪些entry属于该组Q?/p>
?-3-2
从图1-3-1中我们可以看出,用户WANTXIAOMINGQZHANGSANQLISI属于lHR Managers。这U组和成员的关系是通过属性uniquemember来决定的。同时LADP Group 支持嵌套Q即一个组可以是另外一个组的成员,比如我们Accounting Managersl分配给HR Managersl作为其成员Q?/p>
?-3-3
q样表CAccounting Managers中的成员Q同时也是组HR Managers的成员。通过q种层关系可以使权限分配变的更加灵zR?/p>
下面是一些名词的解释Q希望大家对LDAP有更好的理解Q?br />
a) Objectclass —?LDAP对象c,抽象上的概念cM与一般我们理解的class。根据不同的objectclassQ我们可以判断这个entry是否属于某一个类型。比如我们需要找出LDAP中的全部用户Q?objectclass=person)再比如我们需要查询全部的LDAPl:(objectclass=groupOfUniqueNames)
b) Entry —?entry可以被称为条目,或者节点,是LDAP中一个基本的存储单元Q可以被看作是一个DN和一l属性的集合?属性可以定义ؓ多值或者单倹{?br />
c) DN —?Distinguished NameQLDAP中entry的唯一辨别名,一般有如下的Ş式:uid=ZHANGSAN, ou=staff, ou=people, o=examples。LDAP中的entry只有DN是由LDAP Server来保证唯一的?/p>
d) LDAP Search filter ——用filter对LDAPq行搜烦?Filter一般由 (attribute=value) q样的单元组成,比如Q?&(uid=ZHANGSAN)(objectclass=person)) 表示搜烦用户中,uid为ZHANGSAN的LDAP EntryQ再比如Q?&(|(uid= ZHANGSAN)(uid=LISI))(objectclass=person))Q表C搜索uid为ZHANGSAN, 或者LISI的用P也可以?来表CZQ意一个| 比如(uid=ZHANG*SAN)Q搜索uidg ZHANG开头SANl尾的Entry。更q一步,Ҏ不同的LDAP属性匹配规则,可以有如下的FilterQ?(&Qcreatetimestamp>=20050301000000Q?createtimestamp<=20050302000000))Q表C搜索创建时间在20050301000000?0050302000000之间的entry?br /> Filter?“&” 表示“?#8221;Q?#8220;!”表示“?#8221;Q?#8220;|”表示“?#8221;。根据不同的匚w规则Q我们可以?#8220;=”Q?#8220;~=”Q?#8220;>=”以及“<=”Q更多关于LDAP Filter读者可以参考LDAP相关协议Q?a target="_blank">http://www.ietf.org/rfc/rfc2254.txt?/p>
e) Base DN —?执行LDAP Search时一般要指定basednQ由于LDAP是树状数据结构,指定basedn后,搜烦从BaseDN开始,我们可以指定Search Scope为:只搜索basednQbaseQ,basedn直接下Qone levelQ,和basedn全部下Qsub tree levelQ?/p>
下面是一个典型的LDAP Treel构Q右侧显CEntry uid=ZHANGSAN, ou=staff, ou=people, o=examples的属性,该entry代表了一个名字叫张三的用P
2 JAAS和WebLogic Security Framework
现在来多的h开始了解JAASQ用JAAS。WebLogic Security Framework是ZJAAS的。因此我们需要对此有一个非常准的理解才能够设计开发Custom Authentication Provider?/p>
下面我们从几个名词入手,了解JAAS?WebLogic Security Framework的关键之处:
2.1 PrincipalQSubject和LoginModule
a) Principal
当用h功验证后Q系l将会生成与该用户关联的各种Principal。我们这里将Principalq行化的设计Q认Z个Principal是用户d帐号和它所属于的组QLDAP GroupQ。这样当用户d成功后,我们会在LDAP中执行搜索,扑և用户属于哪些l,q将q些l的名字Q或者其标识作ؓPrincipalq回。这P当用户在LDAP中属于某一个组Qƈ且这个组的名字对应到 web.xml Q或者ejb-jar.xmlQ中的Security roleQ那么这个用户就可以看作拥有讉Kq个Security Role定义的资源的权限?br />
在WebLogic Security Framework中,q个LDAP Group的名字(PrincipalQ和Security Role的映关p,可以通过一?Role Mapping Provider来实现动态的匚wQ即用户的动态权限控制。比如在q行时根据某一个业务逻辑来决定用h否拥有某一个权限。关于Role Mapping ProviderQ读者可以参考下面链接的内容Q?a target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542?/p>
b) Subject
JAAS规定由Subject装用户以及用户认证信息Q其中包括Principals。下面是WebLogic Security Framework中Subject的组成图C:
?-1-1
q样当用戯图访问一个受限的J2EE资源Ӟ比如一个web URLQ或者一?EJB MethodQ可以在web.xml或者ejb-jar.xml中定义,由Security Role控制Q,WebLogic Security Framework会通过 Authorization Provider查用户当前的Subject中是否包含有是否可以讉K受限资源的Principals。由于Principals和J2EE Security Role在weblogic.xml中定义一个映关p(或者通过其他业务逻辑来确定这U关p)Q因此通过q样的关p,可以最l知道用h否有某一个J2EE Resource的访问权限?/p>
c) LoginModule
JAAS LoginModule是一个Authentication Provider必须的组成部分。LoginModule是认证的核心引擎Q它负责对用戯n份进行验证,同时返回与用户兌的PrincipalsQ用L录帐P以及LDAP GroupsQ,然后攑օSubject中,供后l的讉K控制使用?br />
我们在LoginModules中完成LDAP的相兌证,查询操作Q将用户在LDAP中所属于的组搜烦出来Q作证后的结果封装到Subject中返回?/p>
2.2 WebLogic Authentication认证q程
下面我们了解一下WebLogic的认证过E。以下图片来?a target="_blank">http://e-docs.bea.com/wls/docs81/dvspisec/atn.html 我将其中主要部分q行说明?/p>
?-2-1
Security Framework在WebLogic Server启动时初始化Authentication ProviderQ?Q。当有认证请求进入时QSecurity Framework首先通过AuthenticationProvider.getLoginModuleConfiguration()来获取一个AppConfigurationEntry对象。通过AppConfigurationEntryQ详?a target="_blank">http://java.sun.com/security/jaas/apidoc/javax/security/auth/login/AppConfigurationEntry.html Q可以初始化一个LoginModule。初始化LoginModule的方法ؓQpublic void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)Q可以看到里面有Subject, CallbackHandler{等重要参数?br />
被实例化的JAAS LoginModule完成用L一pd验证d。验证完成后Q在Q?aQ中principals被Principal Validation Provider{Q在Q?6bQ中存放C用户兌的Subject里面。Security Framework最后将拿着该用LSubjectd成其他权限控制等d?a id="3" name="3">
3 了解WebLogic LDAP Authentication Provider
现在我们有信心了解一下WebLogic LDAP Authentication Provider的工作原理了。这里将以WebLogic提供的iPlanet Authentication Provider的配|ؓ例进行说明。在q里也需要明说明一下,Z方便q行描述Q我们将实际属于LoginModule的行Z一q归l到Provider中。没有单独将两个的行为分开Q目的是ZH出整个完整的过E?/p>
3.1 iPlanet Authentication Provider配置
?-1-1
从上囑֏以看出我们需要指定LDAP服务器的地址Q端口,q接LDAP使用的PrincipalQ不同于前面讨论的PrincipalQ这个Principal实际是一个连接LDAP的用P也就是一个LDAP 中用户Entry?DNQ它必须要有相关的LDAP 搜烦{权限)和CredentialQ一般来说就是口令)?br /> 再看下面关于Users的配|:
?-1-2
3.1.1 User Object Class —?前面已经对objectclassq行q说明,指明LDAP Entry属于哪一c?br />
3.1.2 User Name Attribute —?用户d帐号在LDAP Entry中的属性,一般ؓUID或者cn
3.1.3 User Base DN —?所有的用户会攄到这个子树下面,因此在Provider中对用户q行的搜索将会从q个basedn开?br />
3.1.4 User Search Scope —?指定搜烦范围为Basedn的直接一U或者全部下U?br />
3.1.5 User From Name Filter —?使用q个filter可以搜烦出用户信息。其?u会被用戯入的d帐号替换Q从而查询中LDAP中的用户信息
?-1-3
3.1.6 Group Base DN —?从该Base DN开始搜索用L
3.1.7 Group From Name Filter —?%g会被组的名字替换,通过该filter可以搜烦出符合条件的LDAP Group
3.1.8 Static group name attribute —?l名字的属性,属性cn对应的值就是组的名?/p>
?-1-4
3.1.9 Static Member DN Attribute —?静态成员属性,通过该属性可以判断一个Entry是否属于一个组
3.1.10 Static Group DNs From Member DN Filter —?通过该filter可以扑և用户属于哪些l?br />
3.1.11 Dynamic Group —?动态组是在q行时根据某U业务逻辑Q来军_成员隶属关系的LDAP Group
3.2 iPlanet Authentication Provider的工作原?/strong>
从上面配|的介绍中可以看出,后端存储为LDAP的情况下Q在Console中我们需要配|的参数已经清楚的表明它所要完成工作的内容?br />
首先Q它使用配置的User BaseDN和FilterQ来Ҏ用户输入的登录帐可行搜索,扑և存放在LDAP中的用户Entry。如果找C个用P那么Provider׃用该用户的DN和用戯入的口oQ进行验证。验证可以用LDAP Bind和LDAP CompareQ这需要根据不同LDAP的特Ҏq行选择?br />
A. LDAP Bind —?当前的LDAP Connectionl定C个用戯n份上。这样后l的使用该Connection的LDAP Operation都将以该w䆾q行。LDAP Bind需要两个重要参敎ͼ一个是用户Entry的DNQ一个是该用L口o?/em>
B. LDAP Compare —?LDAP Compare是一个ؓ兼容X.500的古老操作,它用于检查一个属性值是否包含在指定Entry中的属性里。这h们可以在知道用户password存放在哪个属性的前提下,对该属性进行compare。如果成功,表明口o正确。如果属性gؓ散列后的口oQ绝大多数情况)Q有的LDAP Server支持q样的验证,有的不支持,比如iPlanet LDAP Server 5?/em>
验证成功后,Provider用Console中关于Groups和Memberships中的配置Q查扄户属于哪些LDAP GroupQ而且׃q些l本w可能会有一些嵌套,因此对于搜烦到的l还需要进行查询。即使用filterQ?(&Quniquemember=uid=ZHANGSAN,ou=staff,ou=people,o=examplesQ?objectclass=groupOfUniqueNames))从Group Base DN开始搜索,返回用h属的W一层次GroupQ然后对于这些返回的lDNQ仍焉要用上面的Filterq行搜烦Quniquemember值替换ؓl的DNQ,扑և嵌套关系Q直到查询完成没有组嵌套为止Q此处需要防止陷入嵌套的循环中,比如Group A 包含了Group BQ?Group B又包含了Group AQ有的LDAP Server可以自动出Q有的需要我们程序来判断Q?br />
然后用L录的帐号Q用h属组的名字(属性CN的值或其他Q,攑օSubject中。最后调用Principal Validator ProviderQ对Subject中的principalsq行{Q来表明该Subject是这个Provider生成的。这样防止其他攻击者伪造Subject以及Principalq行ƺ骗。此处也可以解释Z在不同的WLS Domain间不能够传递SubjectQ我们可以通过讄域信L完成q种Subject的传递。设|域信Q使用的Credential是{用的Key?br />
?-1-5表明了如何设|WebLogic Domain CredentialQ默认情况下WebLogic Server会在启动的时候随即生成一个CredentialsQ在WLS6.1Ӟq个值就是system用户的口令)Q?/p>
可以惌如果我们实现自己的Principal Validator ProviderQ让它去一个集中的验证服务器中对Subjectq行{Q或者验证SubjectQ这样就可以实现域信任,q而完成ApplicationQEJB TierQ层的SSO?/p>
通过以上的讨论,我们对于实现自己的LDAP Authentication Provider是不是又增加了一份信心?
4 定制自己的Custom LDAP Authentication Provider
Z要定制自qAuthentication Provider? ׃WebLogic Server已经提供了很多默认的Authentication Provider在一般情况下我们实没有必要实现自己的Provider。但是面Ҏ些针对安全方面的复杂需求时QWebLogic Server提供的Provider很有可能不满些需求,此时需要我们定制自qProvider?/p>
在这一章的开头部分中我需要简要讨论关于WebLogic MBean TypesQ以及WebLogic Console扩展{内容,目的在于让读者了解到我们通过WebLogic Console可以完成对Custom Security Provider的配|和部vQ我以WebLogic 提供的Sample Security Provider为示例进行说明。详l的信息可以参考以下的一些资源:
http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106272
http://e-docs.bea.com/wls/docs81/dvspisec/atn.html#1106241
上面两个链接描述了如何创建MBean Types以及在控制台上配|Custom Authentication Provider.下面q个链接中专门介l了WebLogic Console的扩展:
读者可以从http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp下蝲WLS提供的Sample Security Provider?/p>
4.1 MBean Types和WebLogic Console
一般情况下Qh们可能更习惯通过WebLogic Console对Security Providerq行配置。这里我简要描q这个过E,以及可以到达的一个效果。限于篇q就不详l讨Z?/p>
从weblogic.management.security.authentication.Authenticator扩展MBean Types?MBean Types是MBeanQhttp://java.sun.com/products/JavaManagement/wp/Q的工厂Q我们扩展SampleSecurityProviders81 包中的SimpleSampleAuthenticator.xmlQMBean Definition FileQ,增加一个我们自定义的参数LDAP Server IPQ?/p>
<MBeanAttribute Name = "LDAPServerIP" Type = "java.lang.String" Writeable = "true" Default = ""127.0.0.1"" /> |
q样在Provider中我们将通过MbeanMaker(WLS提供)生成的SimpleSampleAuthenticatorMBean中取到这个属性:SimpleSampleAuthenticatorMBean.getLDAPServerIP()。MBean在初始化Provider的时候作为参C入。这h们就可以通过MBean中的参数控制Provider的行为?/p>
当然q个参数是可以在WebLogic Console中设|的Q通过对MBean Types的扩展,在WebLogic Console上看到的画面如下Q?/p>
?-1-1
q样我们可以通过Console修改配置参数Q修改的Security Provider参数保存在config.xml中,默认的值将保存在MBean Jar File中)?/p>
4.2 Z定制LDAP Authentication Provider
当我们面临越来越复杂的安全方面的业务需求时Q或者面临较高的性能要求Q需要根据目标LDAP做针Ҏ的优化Ӟ或者需要将我们已有的认证,或授权模块集成到WebLogicq_ӞWebLogic提供的现成的Provider往往不能满我们的需求?/p>
4.2.1 复杂的业务需?br /> 当系l要求用户不仅仅输入用户名(j_usernameQ,口oQj_passwordQ,q需要输入其他信息,比如d的地点,pȝ的名字,用户的类型等{。如果是采用ZJ2EE Form的验证方式, d信息需要提交到j_security_checkQServlet规范定义由容器负责实现的ServletQ,D我们没法处理更多的信息?/p>
q个时候,如果能够实现我们自己?Authentication ProviderQ那么我们就可以通过TextInputCallback来获取登录表单中更多的信息了Q进而通过q些信息在Provider中完成符合我们需要的处理?/p>
比如搜狐的登录页面上需要选择用户的类型:
?-2-1
4.2.2 性能需求或者调?/strong> 比如Q有的LDAP Server支持动态组QLDAP Dynamic GroupQ成员关pLq行时根据ldap serverQbasednQfilter{动态决定的Q,可以使用如下的Filter查询用户属于哪些动态组Q?/p>
(uniquemember=uid=MAXQ,ou=staff,ou=people,o=examples) 有的LDAP Server虽然支持动态组Q但是支持的有限Q不能用上面的Filter获取用户属于哪些动态组。在WebLogic iPlanet Authentication Provider的实CQ它先是搜烦出全部的动态组Q然后再遍历q些动态组Q依ơ去LDAP中检查用h否属于一个组Q很明显Q这栯然最大程度的满了不同LDAP Server的要求(从品的角度讲可能是必须的)Q但是与LDAP交互的次数大增,q发用户量一大性能下降的比较明显?/p>
此时Q如果系l中的LDAP支持上面的Filter或者有更好的搜索方式,那么完全可以通过定制Provider完成Ҏ能的优化?/p>
4.2.3 已有权限控制的集?/strong> lgQ我只是列D了一些可以驱动我们开发自己Provider的需求,怿在读者实际工作中可能会面临更复杂的情况,开发自qProvider是一个非常好的选择?/p>
4.3 LDAP Authentication Provider实现 WebLogic SecurityFramework通过Authentication Provider获取具体的JAAS LoginModule。通过LoginModule完成最l登录的工作。因此我们必d实现一个AuthenticationProvider?/p>
我们一般通过weblogic.security.spi.AuthenticationProvider 来实现自qAuthenticationProvider。这里介l其中的几个重要ҎQ?/p>
a) public void initialize(ProviderMBean mbean, SecurityServices services) b) public void shutdown() c) public AppConfigurationEntry getLoginModuleConfiguration() 其中LoginModule Name?/p>
?examples.security.providers.authentication.SampleLoginModuleImpl"Q我们通过它就可以实例化一个LoginModuleq过LoginModule.initialize()Ҏq行初始化?/p>
d) public AppConfigurationEntry getAssertionModuleConfiguration() Provider的实现比较简单,读者可以在http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp下蝲WebLogic提供的SamplesQ查看SampleAuthenticationProviderImpl的代码?/p>
4.4 LDAP LoginModule 逻辑程 4.5 LoginModule代码CZ和讲?br />
q里我将使用Netscape LDAP SDK for java作ؓ开发工具实现LDAP相关的操作,读者可以到http://docs.sun.com/db/doc/816-6402-10 下蝲开发手册,?a target="_blank">http://www.mozilla.org/directory/ 下蝲SDK 包。一般来说还可以通过JNDI来操作LDAPQ我个h认ؓSun LDAP JNDI Provider中关于Connection Pool的实现非怼U。但不管使用哪种SDKQ对LDAP的编E原理上基本都是相同的(因ؓZ同样的LDAP协议Q,不同的可能仅仅是接口Q类的名字而已?/p>
4.5.1 初始化Connection Pool Sun JNDI LDAP Service ProviderQJDK1.4Q中可以通过在环境变量中讄具体的参数来启用q接池,辑ֈ复用q接的目的。具体可以参考链接:http://java.sun.com/products/jndi/tutorial/ldap/connect/index.htmlQ下面是CZ代码Q?br />
4.5.2 Ҏ用户输入的登录帐P搜烦用户Entry 首先需要从池中获取一个LDAPq接Q然后用LDAPConnection.searchҎq行搜烦。我q里以一个典型的LDAP Search接口Zq行说明Q其他API比如JNDI{,Z同样的LDAP协议Q接口中同样参数的含义都是相同的?/p>
1) Base: 表明从该Basedn开始搜索,可以通过MBean获取 2) Scope: 搜烦的范_ a) LDAPv2.SCOPE_BASEQ?只搜索basedn指定的entry 3) FilterQ过虑条件。比如uid=ZHANGSANQ搜索UID为ZHANGSAN的用P再比如uid=ZHANG*Q搜?ZHANG开头的帐号Q或者uid=Z*SQ搜索以Z开头Sl尾的帐受前面已l介l过q里׃多说了?/p>
4) attrsQ返回该attrs数组中指定的属性,比如new String[]{“uid”}Q只q回属性uidQ其他属性将会不在结果中q回?一般来说我们会要求开发h员只需要的属性返回,q样避免q回无用的属性,降低|络和Server{方面的资源开销Q而且如果存在一个有较大属性集合的EntryQƈ且你q不使用到这个较大的属性集合。D个实际例子来_比如你的pȝ中拥有很多成员数目超q?万或者更多的LDAP GroupsQƈ且你希望通过LDAP Search扑և某一个用户属于的lCNQ那么搜索结果只q回l的CN已经可以满你的要求了,q时没有必要将全部member属性也q回了。这在后面我会有代码来说明?/p>
q里q有一点很重要的细节,怿对读者会有帮助。比如,你希望搜索到modifytimestamp{Operational Attributes。这些属性(LDAP Server自己负责l护的,用户无法修改的)必须要在参数attrs中指定,LDAP Server才会q回l客L。如果用户希望返回全部User Attributes的同Ӟq回指定?Operational Attributes那该怎么办?不在attrs列表中的属性将不会被返回,一旦我指定了attrsQ是否需要将全部的属性都列在attrs参数中?实际上此时只需要传?new String[]{ “*”, “createtimestamp”}q样的参数即可;“*”号即代表了全部的User Attributes。在Sun JNDI LDAP Service Provider中也是这P管NSun关于JNDI的文档也找不到这L说明。LDAP协议Ҏ的说明在 http://www.ietf.org/rfc/rfc2251.txt W?8c?/p>
5) attrsOnlyQ只q回属性名Uͼ不包含倹{我们一般设|ؓfalse?/p>
我们下面看一下Sun JNDI中关于LDAP Search的接口: public NamingEnumeration search(String name, 其他的参数还包括Size LimitQ即限制搜烦l果的数目(LDAPq回的Entry一般情况下是没有排序的Q除非用一些Sort ControlQ,当你的搜索可能会q回成千上万的EntryӞ限制搜烦的数目是非常明智也是必须的。关于这些,读者可以参考API文档或者LDAP协议?/p>
4.5.3 Ҏ用户的Entry DNQ和用户输入的口令完成n份验?br />
下面的方法实C到LDAP的n份验证。LDAP中的用户实际上就是一个LDAP EntryQ一ơ验证实际是Connection与这个Entryq行l定Q因此验证时我们需要两个必ȝ参数Q一个是Entry的DNQ一个是口o。LDAP的n份验证方式有多种Q包括SASL以及Z证书的验证等Q我们这里只介绍Simple Authentication。关于SASL更多信息读者可以参考:http://www.ietf.org/rfc/rfc2222.txt?/p>
比如iPlanet LDAP Server 5Q只能通过Bind完成认证Q而Oracle Internet Directory可以通过LDAP Compare完成验证Q而且q支持口令策略?/p>
如果我们使用了连接池Q连接池中的q接一般都是用权限较大的用户初始化的Q这栯些连接才可以完成对LDAP的搜索操作;而当通过q些q接Ҏ通用戯行n份验证时Q如果通过验证Q连接的w䆾被改变为普通的用户Q或UCؓ与普通用Lw䆾兌Q。普通用户很可能没有除了bind以外的Q何权限,所以在q接被放入池中前Q我们必要恢复q接的n份?/p>
q样我们必须执行两次LDAP BindQ一ơ用于对普通用户验证n份;一ơ用于恢复连接的较大权限的用戯n份。我们看到这h率是比较低的Q可能你在LDAP Server端统计有2万次bindhQ实际上只有1万hơ登录?/p>
对于特定的LDAP ServerQ比?Oracle Internet DirectoryQ可以通过LDAP Compare对用戯n份进行验证,q且不会改变q接兌的用戯n份。这h在用池的情况下只需要一ơLDAP Compare卛_。效率有很大提高。如果不通过定制LDAP Authentication ProviderQ这L调优是没法实现的?/p>
Netscape LDAP SDK的ConnectionPool的实CQ在q接攑օ池中前会查连接的w䆾Q如果n份被改变Q那么会重新q行bind。所以我们没有必要在代码中再做bind?/p>
4.5.4 Ҏ用户的DN搜烦用户属于的组列表 成员和组的membership主要是通过uniquemember或?member属性来定义的。成员不仅仅可以使用用户Q也可以是组Q因为组可以嵌套?br />
b) l成员的数量可能比较大,Z避免不必要的开销Q我们指定只q回l的cn属性?/p>
c) 动态组的优化。在WebLogic默认的实CQProviderQ实际ؓLoginModuleQ会不断的拿动态组中定义的URL中的filter和用LDN去LDAP做搜索,来看用户是否属于该组。本文前面也讨论了,q样效率很低Q完全可以放在Provider本地实现URL和Entry的匹配?/p>
4.5.5 LoginModule中的login()Ҏ实现 a) 用于保存验证l果Q在后箋ҎQcommit{)中?br />
下面的代码将演示q种技术: q样我们通过((TextInputCallback)callbacks[2]).getText()来获取表单中更多的信息。其中TextInputCallback的Prompt必须要和表单中对应域的名字一_否则取不C息。如果有多个域,则需要传入多个TextInputCallback?/p>
同时如果d表单中没有这些对应TextInputCallback的域Q或者用JNDI方式验证Q那么会抛出UnsupportedCallbackException?/p>
下面是一个符合Servlet安全规范的登录表单,其中域my_hidden_field的值将通过CallbackHandler传递到的TextInputCallback中: d) 生成一个用于存放Principal的集合对?br />
e) 通过d帐号获取LDAP中的Entry DN 4.5.6 LoginModule中的commit()和abort()Ҏ实现 我这里仍焉过代码中的注释q行相应的说明: a) JAAS SubjectQ在LoginModule初始化时Q由WebLogic Security Framework负责传入Q用L权限信息QPrincipals{)都将装到该Subject中。同时Principal Validator会对Subject中的Principalsq行{Q防止被黑客U改?/p>
b) 用户认证成功的情况下Q将用户兌的Principals加入到Subject中。这L户在q行后箋的访问时QWebLogic Security Framework会检查与用户兌的Subject中是否有可以讉K受限资源的Principal?/p>
c) 用户dp|的情况下Q有可能是其他的LoginModuleD的整体登录失败,具体参考JAAS Control FlagQ,我们在commit中添加的Principals必须从Subject中删除?/p>
同时׃使用了Provider MBeanQ因此,当你删除了MBean Types中定义的Qƈ且通过修改保存在config.xmlQWebLogic Domain配置文gQ中的属性时Q重新部|的Jar包会DWebLogic Server无法正常启动Q因为它没有办法按照config.xml中配|的属性初始化MBean。此旉要手工修改config.xmlQ删除相关的属性即可?/p>
<examples.security.providers.authentication.simple.SimpleSampleAuthenticator 比如UserBaseDN已经不需要通过MBean来管理,q且在MBean Types中已l删除,那么直接删除q里的UserBaseDN="ou=people, o=examples" 可以了?a id="6" name="6"> 限于幅Q本文很多地方的表述可能q不清晰Q也可能会有一些错误,如果在阅读过E中产生M疑问或者有M看法都可以通过E-mail来与我交,我也非常希望能和大家交流?a id="7" name="7">
有时Q有的用户会比较困惑ZWebLogic LDAP Security Provider在压力测试中的表C很理惻I用户需要较长时间的{待Q才能够d到系l中。由于这些Provider?WebLogic产品的一部分Q因此缺乏对不同目标LDAP Server的有针对性的优化。这样就使得我们无法充分发挥具体LDAP Server的性能调优?/p>
如果pȝ中已l存在了现成的满需求的认证模块Qƈ且已l很好的工作Q在pȝ转向J2EE架构Qƈ使用WebLogic Server做J2EE容器Ӟ我们可能会更愿意直接在Provider中加入这个认证模块?/p>
本文之前Z表述的方便没有单独提到LoginModuleQ认为LoginModule的行为就是LDAP Authentication Provider的行为。到了目前的具体实现阶段Q我们必d开Authentication Provider和JAAS LoginModule。最l部|到WebLogic上的实际只是LDAP AuthenticationProvider Implements?/p>
初始化一个Provider。通过参数MBean我们可以获取到在WebLogic Console中配|的各项参数。进而初始化我们的ProviderQ然后通过Provider传递到LoginModule中?/p>
释放一些与ProviderQLoginModule{相关的资源?/p>
q个Ҏ非常重要Q通过该方法,WebLogic Security Framework可以获取用于初始化LoginModule的AppConfigurationEntry。AppConfigurationEntry中存放了LoginModule的类名等信息Q比如用如下代码返回一个AppConfigurationEntry:
return new AppConfigurationEntry(
"examples.security.providers.authentication.SampleLoginModuleImpl",
controlFlag,
options);
该方法将q回一个与Identity Assertion Provider兌的LoginModule。这个Assertion LoginModuleQ将只会验证用户是否存在Q以及如果存在返回用LPrincipals?该方法也比较重要Q需要正实玎ͼ比如我们使用CLIENT-CERTq种WEB认证方式Q该Ҏ׃被调用?/p>
实现了Provider后,必须拥有我们自己的LDAP LoginModule。下面是一个简单的用于演示的验证逻辑程图。实际的一个LoginModule׃不同的业务需求,情况可能会复杂得多。这里只是描qC最核心最基本的逻辑Q读者能有一个清晰的思\。后面我以q个程Zq行实现?/p>
Z有效使用宝贵Qƈ且有限的LDAPq接Q必M用连接池。下面的代码初始化了一个LDAPq接池:
/**
*
*/
static ConnectionPool pool;
/** * * @throw LDAPException
*/ public LdapClient()
throws LDAPException
{
pool= new ConnectionPool(
1, 150, "127.0.0.1", 389, "cn=Directory Manager", "88888888");
}
Hashtable env= new Hashtable();
…
env.put("com.sun.jndi.ldap.connect.pool", "true");
DirContext ctx= new InitialDirContext( env);
下面q个Ҏ实现了从LDAP中搜索用户Entry DN的最单的q程。实际上我们可以在其中实现很多定制的功能。比如允许用户用多于一个的帐号dQ只要这些帐可够通过LDAP Searche最l返回一个唯一的用户DN卛_?/p>
* @return
* @throws LDAPException
*/
public String getUserDN( String uid) throws LDAPException{
LDAPConnection conn= pool.getConnection();
try {
String[] attrs= new String[]
{};
LDAPSearchResults sr= conn.search("o=examples", 2, "(uid="+ uid +")", attrs, false);
if ( sr.hasMoreElements()) {
LDAPEntry entry= sr.next();
return entry.getDN();
}
throw new LDAPException("No Such Object:"+ uid,
LDAPException.NO_SUCH_OBJECT);
}catch ( LDAPException ex) {
throw ex;
}finally {
try {
if (conn!= null) pool.close(conn);
}catch ( Exception ex)
{}
}
}
public LDAPSearchResults search(java.lang.String base,
int scope,
java.lang.String filter,
java.lang.String[] attrs,
boolean attrsOnly)
b) LDAPv2.SCOPE_ONE, 在basedn的下一Uentry中搜索,不包括basedn
c) LDAPv3.SCOPE_SUBQ在basedn的全部下Uentry中搜索,包括basedn
String filter,
SearchControls cons)
throws NamingException
name参数是上面的basednQSearchControls中封装更多的讄Q比如:SearchControls. setSearchScope(int scope)其中的scope对应上面?)Q?SearchControls.setReturningAttributes(String[] attrs)Q设|指定返回的属性,对应上面?)?/p>
/**
*
* @param dn
* @param pwd
* @return
* @throws LDAPException
*/
public boolean authentication( String dn, String pwd) throws LDAPException {
LDAPConnection conn= pool.getConnection();
try {
conn.authenticate( dn, pwd);
return true;
}catch ( LDAPException ex) {
if ( ex.getLDAPResultCode()== LDAPException.INVALID_CREDENTIALS) {
return false; // 用户口o错误 }
if ( ex.getLDAPResultCode()== LDAPException.NO_SUCH_OBJECT) {
return false; // 用户不存?}
throw ex;
}finally {
try {
if ( conn!= null) pool.close(conn);
}catch ( Exception ex) {
}
}
}
我们q里使用 LDAP Bind操作完成对用Lw䆾验证。本文前面曾提到也可以用LDAP CompareQ通过比较口o属性(userpasswordQ中的值来完成验证。我们需要根据不同的LDAP Server选择合适的验证Ҏ?/p>
有了上面的基后,q个Ҏ很Ҏ理解了。下面我们来看看如何q回用户所属的l?/p>
/**
*
* @param groupbasedn
* @param memberDn
* @return
* @throws LDAPException
*/
public List getGroupMembership( String groupbasedn, String memberDn) throws LDAPException {
LDAPConnection conn= pool.getConnection();
try {
LDAPSearchResults sr= conn.search(
groupbasedn,
2,
"(uniquemember="+ memberDn +")",
new String[] {"cn"},
false);
List groups= new java.util.ArrayList();
while ( sr.hasMoreElements()) {
LDAPEntry entry= sr.next();
LDAPAttribute attr= entry.getAttribute("cn");
if ( attr!= null) {
String[] values= attr.getStringValueArray();
if ( values!= null && values.length>0) groups.add( values[0]);
}
}
return groups;
}catch ( LDAPException ex) {
throw ex;
}finally {
try {
if ( conn!= null) pool.close(conn);
}catch ( Exception ex) {
}
}
}
a) 上面的方法中只实C一个层ơ的搜烦Q即用户——组的搜索,而组间的嵌套搜烦没有实现。读者可以根据系l内的具体情况,在此处也可以做一些优化?/p>
一切准备就l后Q我们就可以完成LoginModule.login()q个最核心的方法了。下面我Ҏ代码中的注释逐条说明?/p>
// A
boolean loginSucceeded;
// B
List principals= new java.util.ArrayList();
/**
*
* @return
* @throws LoginException
*/
public boolean login() throws LoginException {
// C
Callback[] callbacks= new Callback[] {
new NameCallback("username: "),
new PasswordCallback("password: ",false)};
try {
callbackHandler.handle( callbacks);
}catch (IOException e) {
throw new LoginException(e.toString());
}catch (UnsupportedCallbackException e) {
throw new LoginException(e.toString() + " " +e.getCallback().toString()); }
//
String userName = ((NameCallback)callbacks[0]).getName();
if ( userName== null || userName.length()== 0) {
throw new LoginException("User login name is empty!");
}
//
PasswordCallback passwordCallback= (PasswordCallback)callbacks[1]; char[] password = passwordCallback.getPassword();
passwordCallback.clearPassword();
if ( password== null || password.length== 0) {
throw new LoginException("User password is empty!");
}
try {
// D
String dn= this.getUserDN( userName);
if ( dn== null) {
throw new LoginException("User "+ userName +" doesn't exist.");
}
// E
boolean authResult= this.authentication( dn, String.valueOf( password)); if ( authResult== false) {
throw new FailedLoginException("User login failed.");
}
// F
principals.add( new WLSUserImpl( userName));
// G
List groups= this.getGroupMembership( "ou=groups,o=examples", dn); for ( int i=0, n=groups.size(); i<n; i++) {
String cn= String.valueOf(groups.get(i));
// H
principals.add( new WLSGroupImpl(cn));
}
}catch ( LDAPException ex) {
java.io.StringWriter sw = new java.io.StringWriter();
ex.printStackTrace(new java.io.PrintWriter(sw));
sw.flush();
throw new LoginException( sw.toString());
}
return loginSucceeded= true;
}
b) 用于保存和用户关联的PrincipalQ在后箋ҎQcommit{)中?br />
c) 在JAAS中通过CallbackHandler来完成用户和底层安全认证pȝ间信息的交换Q这里我们通过CallbackHandler获取用户输入的登录帐号和口o。CallbackHandler在初始化LoginModule时由WebLogic Security Framework传入。读者可能会问,我是否可以在此要求WebLogic Security Framework传入我自己实现的CallbackHandlerQ进而获取更多的信息Q支持更多的CallbackQ很遗憾Q不可以。只有在一U情况下Q本文前面也提到Q有一个变通,是在Web应用中,通过Form Basedq种认证方式q行用户w䆾验证Ӟ可以获取更多的登录表单中提交的cgi变量?/p>
Callback[] callbacks= new Callback[] {
new NameCallback("username: "),
new PasswordCallback("password: ",false),
new TextInputCallback("my_hidden_field")};
try {
callbackHandler.handle( callbacks);
}catch (IOException e) {
throw new LoginException(e.toString());
}catch (UnsupportedCallbackException e) {
throw new LoginException(e.toString() + " " +e.getCallback().toString()); }
String value= ((TextInputCallback)callbacks[2]).getText();
<html>
<form method=”POST” action=”j_security_check”>
<input type=”text” name=”j_username”>
<input type=”password” name=”j_password”>
<input type=”hidden” name=”my_hidden_field”value=”Hello Callback!”>
</form>
</html>
f) 通过DN和用户口令进行n份验?br />
g) 通过验证的用户名加入到Principal列表中,q些以后都将代表用户的一定权?br />
h) 查找用户属于哪些l?br />
i) 这些组的名字加入到Principal列表?/p>
完成了以上的验证后,我们需要通过LoginModule.commit()Ҏ提交验证l果Q或者abort()Ҏ取消验证l果?/p>
// A
private Subject subject;
/**
*
* @return
* @throws LoginException
*/
public boolean commit() throws LoginException { if (loginSucceeded) {
// B
subject.getPrincipals().addAll(principals);
return true;
}else {
return false;
}
}
/**
*
* @return
* @throws LoginException
*/
public boolean abort() throws LoginException {
// C
subject.getPrincipals().removeAll(principals);
return true;
}
通过上面的描q和讨论Q相信读者已l对如何实现一个LDAP AuthenticationProvider/LoginModule有了比较清楚的了解。限于篇q省略了很多l节Q对此有需要的读者可以根据文中给出的相关链接做进一步的了解Q或者与我交。从q里我们也可以看到核心的逻辑q是比较单的。其中将LDAP Group作ؓ用户的PrincipalQ是J2EE Security与LDAP Securityl合的非帔R要的一步。通过q种l合Q我们可以将LDAP作ؓ后端Q设计出ZJ2EEQJAAS的用戯n份验证,权限理{系l?a id="5" name="5">
5 部v中的注意事项
开发完成LDAP Authentication Provider后,需要将通过WebLogic MBean Maker生成的MBean Jar File攑ֈ${WL_HOME}/server/lib/mbeantypes目录下。由于WebLogic Server在启动时会加载这些ProviderQ因此在一个分布式的环境中QWebLogic Domain内的全部WLServerQ包括Managed ServerQ均需要部|该Jar包?/p>
ControlFlag="OPTIONAL"
Name="Security:Name=myrealmSimpleSampleAuthenticator"
UserBaseDN="ou=people, o=examples" Realm="Security:Name=myrealm"/>
6 l束?/strong>
本文所讨论的内Ҏ些过于广泛,从我个h角度Ԍ写v来也比较困难Q很多技术问题没有进行深入的讨论。其实只是希望通过本文Q能够帮助读者对WebLogic SecurityQJ2EE SecurityQLDAP Security以及JAAS有一个初步的认识Q能够给希望实现LDAP Authentication ProviderQ或者被WebLogic LDAP Authentication Provider困惑的读者一些启C。只要能够对读者有所帮助Q本文的目的q辑ֈ了?/p>
7 参考资?/strong>
a) http://e-docs.bea.com/wls/docs81/secwlres/types.html#1213777 关于WebLogic Resource的更多说?br />
b) http://e-docs.bea.com/wls/docs81/security/index.html 关于WebLogic Security的全部内?br />
c) http://e-docs.bea.com/wls/docs81/dvspisec/rm.html#1145542 关于Role Mapping Provider的更多内?br />
d) http://dev2dev.bea.com.cn/techdoc/2005012102.html 关于WebLogic Console的扩?br />
e) http://dev2dev.bea.com/codelibrary/code/security_prov81.jsp Custom Seuciryt Provider的Samples
f) http://java.sun.com/products/JavaManagement/wp/ 更多关于Sun JMX
g) http://java.sun.com/products/jndi/tutorial/ldap/connect/index.html 关于如何使用Sun JNDI LDAP Service Provider中提供的q接?br />
h) http://www.ietf.org/rfc/rfc2254.txt 更多关于LDAP Filter
i) http://www.ietf.org/rfc/rfc2251.txt LDAP V3协议
j) http://www.ietf.org/rfc/rfc2222.txt 更多关于SASL
]]>