編寫(xiě)你自己的單點(diǎn)登錄(SSO)服務(wù) 2
Web-SSO的源代碼可以從網(wǎng)站地址http://211.151.94.21/blog/yutoujava/resource/websso_src.zip下載。身份認證服務(wù)是一個(gè)標準的web應用,包括一個(gè)名為SSOAuth的Servlet,一個(gè)login.jsp文件和一個(gè)failed.html。身份認證的所有服務(wù)幾乎都由SSOAuth的Servlet來(lái)實(shí)現了;login.jsp用來(lái)顯示登錄的頁(yè)面(如果發(fā)現用戶(hù)還沒(méi)有登錄過(guò));failed.html是用來(lái)顯示登錄失敗的信息(如果用戶(hù)的用戶(hù)名和密碼與信息數據庫中的不一樣)。
|
從代碼很容易看出,SSOAuth就是一個(gè)簡(jiǎn)單的Servlet。其中有兩個(gè)靜態(tài)成員變量:accounts和SSOIDs,這兩個(gè)成員變量都使用了JDK1.5中線(xiàn)程安全的MAP類(lèi): ConcurrentMap,所以這個(gè)樣例一定要JDK1.5才能運行。Accounts用來(lái)存放用戶(hù)的用戶(hù)名和密碼,在init()的方法中可以看到我給系統添加了三個(gè)合法的用戶(hù)。在實(shí)際應用中,accounts應該是去數據庫中或LDAP中獲得,為了簡(jiǎn)單起見(jiàn),在本樣例中我使用了 ConcurrentMap在內存中用程序創(chuàng )建了三個(gè)用戶(hù)。而SSOIDs保存了在用戶(hù)成功的登錄后所產(chǎn)生的cookie和用戶(hù)名的對應關(guān)系。它的功能顯而易見(jiàn):當用戶(hù)成功登錄以后,再次訪(fǎng)問(wèn)別的系統,為了鑒別這個(gè)用戶(hù)請求所帶的cookie的有效性,需要到SSOIDs中檢查這樣的映射關(guān)系是否存在。
在主要的請求處理方法 processRequest()中,可以很清楚的看到SSOAuth的所有功能
如果用戶(hù)還沒(méi)有登錄過(guò),是第一次登錄本系統,會(huì )被跳轉到login.jsp頁(yè)面(在后面會(huì )解釋如何跳轉)。用戶(hù)在提供了用戶(hù)名和密碼以后,就會(huì )用 handlerFromLogin()這個(gè)方法來(lái)驗證。
如果用戶(hù)已經(jīng)登錄過(guò)本系統,再訪(fǎng)問(wèn)別的應用的時(shí)候,是不需要再次登錄的。因為瀏覽器會(huì )將第一次登錄時(shí)產(chǎn)生的cookie和請求一起發(fā)送。效驗cookie的有效性是SSOAuth的主要功能之一。
SSOAuth還能直接效驗非login.jsp頁(yè)面過(guò)來(lái)的用戶(hù)名和密碼的效驗請求。這個(gè)功能是用于非web應用的SSO,這在后面的桌面SSO中會(huì )用到。
SSOAuth還提供logout服務(wù)。
下面看看幾個(gè)主要的功能函數:
|
handlerFromLogin()這個(gè)方法是用來(lái)處理來(lái)自login.jsp的登錄請求。它的邏輯很簡(jiǎn)單:將用戶(hù)輸入的用戶(hù)名和密碼與預先設定好的用戶(hù)集合(存放在accounts中)相比較,如果用戶(hù)名或密碼不匹配的話(huà),則返回登錄失敗的頁(yè)面(failed.html),如果登錄成功的話(huà),需要為用戶(hù)當前的session創(chuàng )建一個(gè)新的ID,并將這個(gè)ID和用戶(hù)名的映射關(guān)系存放到SSOIDs中,最后還要將這個(gè)ID設置為瀏覽器能夠保存的cookie值。
登錄成功后,瀏覽器會(huì )到哪個(gè)頁(yè)面呢?那我們回顧一下我們是如何使用身份認證服務(wù)的。一般來(lái)說(shuō)我們不會(huì )直接訪(fǎng)問(wèn)身份服務(wù)的任何URL,包括login.jsp。身份服務(wù)是用來(lái)保護其他應用服務(wù)的,用戶(hù)一般在訪(fǎng)問(wèn)一個(gè)受SSOAuth保護的Web應用的某個(gè)URL時(shí),當前這個(gè)應用會(huì )發(fā)現當前的用戶(hù)還沒(méi)有登錄,便強制將也頁(yè)面轉向SSOAuth的login.jsp,讓用戶(hù)登錄。如果登錄成功后,應該自動(dòng)的將用戶(hù)的瀏覽器指向用戶(hù)最初想訪(fǎng)問(wèn)的那個(gè)URL。在handlerFromLogin()這個(gè)方法中,我們通過(guò)接收“goto”這個(gè)參數來(lái)保存用戶(hù)最初訪(fǎng)問(wèn)的URL,成功后便重新定向到這個(gè)頁(yè)面中。
另外一個(gè)要說(shuō)明的是,在設置cookie的時(shí)候,我使用了一個(gè)setMaxAge(6000)的方法。這個(gè)方法是用來(lái)設置cookie的有效期,單位是秒。如果不使用這個(gè)方法或者參數為負數的話(huà),當瀏覽器關(guān)閉的時(shí)候,這個(gè)cookie就失效了。在這里我給了很大的值(1000分鐘),導致的行為是:當你關(guān)閉瀏覽器(或者關(guān)機),下次再打開(kāi)瀏覽器訪(fǎng)問(wèn)剛才的應用,只要在1000分鐘之內,就不需要再登錄了。我這樣做是下面要介紹的桌面SSO中所需要的功能。
其他的方法更加簡(jiǎn)單,這里就不多解釋了。
要實(shí)現WEB-SSO的功能,只有身份認證服務(wù)是不夠的。這點(diǎn)很顯然,要想使多個(gè)應用具有單點(diǎn)登錄的功能,還需要每個(gè)應用本身的配合:將自己的身份認證的服務(wù)交給一個(gè)統一的身份認證服務(wù)-SSOAuth。SSOAuth服務(wù)中提供的各個(gè)方法就是供每個(gè)加入SSO的Web應用來(lái)調用的。
一般來(lái)說(shuō),Web應用需要SSO的功能,應該通過(guò)以下的交互過(guò)程來(lái)調用身份認證服務(wù)的提供的認證服務(wù):
Web應用中每一個(gè)需要安全保護的URL在訪(fǎng)問(wèn)以前,都需要進(jìn)行安全檢查,如果發(fā)現沒(méi)有登錄(沒(méi)有發(fā)現認證之后所帶的cookie),就重新定向到SSOAuth中的login.jsp進(jìn)行登錄。
登錄成功后,系統會(huì )自動(dòng)給你的瀏覽器設置cookie,證明你已經(jīng)登錄過(guò)了。
當你再訪(fǎng)問(wèn)這個(gè)應用的需要保護的URL的時(shí)候,系統還是要進(jìn)行安全檢查的,但是這次系統能夠發(fā)現相應的cookie。
有了這個(gè)cookie,還不能證明你就一定有權限訪(fǎng)問(wèn)。因為有可能你已經(jīng)logout,或者cookie已經(jīng)過(guò)期了,或者身份認證服務(wù)重起過(guò),這些情況下,你的cookie都可能無(wú)效。應用系統拿到這個(gè)cookie,還需要調用身份認證的服務(wù),來(lái)判斷cookie時(shí)候真的有效,以及當前的cookie對應的用戶(hù)是誰(shuí)。
如果cookie效驗成功,就允許用戶(hù)訪(fǎng)問(wèn)當前請求的資源。
以上這些功能,可以用很多方法來(lái)實(shí)現:
在每個(gè)被訪(fǎng)問(wèn)的資源中(JSP或Servlet)中都加入身份認證的服務(wù),來(lái)獲得cookie,并且判斷當前用戶(hù)是否登錄過(guò)。不過(guò)這個(gè)笨方法沒(méi)有人會(huì )用:-)。
可以通過(guò)一個(gè)controller,將所有的功能都寫(xiě)到一個(gè)servlet中,然后在URL映射的時(shí)候,映射到所有需要保護的URL集合中(例如*.jsp,/security/*等)。這個(gè)方法可以使用,不過(guò),它的缺點(diǎn)是不能重用。在每個(gè)應用中都要部署一個(gè)相同的servlet。
Filter是比較好的方法。符合Servlet2.3以上的J2EE容器就具有部署filter的功能。(Filter的使用可以參考JavaWolrd的文章http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html)Filter是一個(gè)具有很好的模塊化,可重用的編程API,用在SSO正合適不過(guò)。本樣例就是使用一個(gè)filter來(lái)完成以上的功能。
|
以上的初始化的源代碼有兩點(diǎn)需要說(shuō)明:一是有兩個(gè)需要配置的參數 SSOServiceURL和 SSOLoginPage。因為當前的Web應用很可能和身份認證服務(wù)(SSOAuth)不在同一臺機器上,所以需要讓這個(gè)filter知道身份認證服務(wù)部署的URL,這樣才能去調用它的服務(wù)。另外一點(diǎn)就是由于身份認證的服務(wù)調用是要通過(guò)http協(xié)議來(lái)調用的(在本樣例中是這樣設計的,讀者完全可以設計自己的身份服務(wù),使用別的調用協(xié)議,如RMI或SOAP等等),所有筆者引用了apache的commons工具包(詳細信息情訪(fǎng)問(wèn)apache 的網(wǎng)站http://jakarta.apache.org/commons/index.html),其中的“httpclient”可以大大簡(jiǎn)化http調用的編程。
下面看看filter的主體方法doFilter():
|
doFilter()方法的邏輯也是非常簡(jiǎn)單的,在接收到請求的時(shí)候,先去查找是否存在期望的cookie值,如果找到了,就會(huì )調用SSOService(cookieValue)去效驗這個(gè)cookie的有效性。如果cookie效驗不成功或者cookie根本不存在,就會(huì )直接轉到登錄界面讓用戶(hù)登錄;如果cookie效驗成功,就不會(huì )做任何阻攔,讓此請求進(jìn)行下去。在配置文件中,有下面的一個(gè)節點(diǎn)表示了此filter的URL映射關(guān)系:只攔截所有的jsp請求。
<filter-mapping>
<filter-name>SSOFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
下面還有幾個(gè)主要的函數需要說(shuō)明:
|
這兩個(gè)函數主要是利用apache中的httpclient訪(fǎng)問(wèn)SSOAuth提供的認證服務(wù)來(lái)完成效驗cookie和logout的功能。
其他的函數都很簡(jiǎn)單,有很多都是我的IDE(NetBeans)替我自動(dòng)生成的。
4 當前方案的安全局限性
當前這個(gè)WEB-SSO的方案是一個(gè)比較簡(jiǎn)單的雛形,主要是用來(lái)演示SSO的概念和說(shuō)明SSO技術(shù)的實(shí)現方式。有很多方面還需要完善,其中安全性是非常重要的一個(gè)方面。
我們說(shuō)過(guò),采用SSO技術(shù)的主要目的之一就是加強安全性,降低安全風(fēng)險。因為采用了SSO,在網(wǎng)絡(luò )上傳遞密碼的次數減少,風(fēng)險降低是顯然的,但是當前的方案卻有其他的安全風(fēng)險。由于cookie是一個(gè)用戶(hù)登錄的唯一憑據,對cookie的保護措施是系統安全的重要環(huán)節:
-
cookie的長(cháng)度和復雜度
在本方案中,cookie是有一個(gè)固定的字符串(我的姓名)加上當前的時(shí)間戳。這樣的cookie很容易被偽造和猜測。懷有惡意的用戶(hù)如果猜測到合法的cookie就可以被當作已經(jīng)登錄的用戶(hù),任意訪(fǎng)問(wèn)權限范圍內的資源
-
cookie的效驗和保護
在本方案中,雖然密碼只要傳輸一次就夠了,可cookie在網(wǎng)絡(luò )中是經(jīng)常傳來(lái)傳去。一些網(wǎng)絡(luò )探測工具(如sniff, snoop,tcpdump等)可以很容易捕獲到cookie的數值。在本方案中,并沒(méi)有考慮cookie在傳輸時(shí)候的保護。另外對cookie的效驗也過(guò)于簡(jiǎn)單,并不去檢查發(fā)送cookie的來(lái)源究竟是不是cookie最初的擁有者,也就是說(shuō)無(wú)法區分正常的用戶(hù)和仿造cookie的用戶(hù)。
-
當其中一個(gè)應用的安全性不好,其他所有的應用都會(huì )受到安全威脅
因為有SSO,所以當某個(gè)處于 SSO的應用被黒客攻破,那么很容易攻破其他處于同一個(gè)SSO保護的應用。
這些安全漏洞在商業(yè)的SSO解決方案中都會(huì )有所考慮,提供相關(guān)的安全措施和保護手段,例如Sun公司的Access Manager,cookie的復雜讀和對cookie的保護都做得非常好。另外在OpneSSO (https://opensso.dev.java.net)的架構指南中也給出了部分安全措施的解決方案。
5 當前方案的功能和性能局限性
除了安全性,當前方案在功能和性能上都需要很多的改進(jìn):
-
當前所提供的登錄認證模式只有一種:用戶(hù)名和密碼,而且為了簡(jiǎn)單,將用戶(hù)名和密碼放在內存當中。事實(shí)上,用戶(hù)身份信息的來(lái)源應該是多種多樣的,可以是來(lái)自數據庫中,LDAP中,甚至于來(lái)自操作系統自身的用戶(hù)列表。還有很多其他的認證模式都是商務(wù)應用不可缺少的,因此SSO的解決方案應該包括各種認證的模式,包括數字證書(shū),Radius, SafeWord ,MemberShip,SecurID等多種方式。最為靈活的方式應該允許可插入的JAAS框架來(lái)擴展身份認證的接口
-
我們編寫(xiě)的Filter只能用于J2EE的應用,而對于大量非Java的Web應用,卻無(wú)法提供SSO服務(wù)。
-
在將Filter應用到Web應用的時(shí)候,需要對容器上的每一個(gè)應用都要做相應的修改,重新部署。而更加流行的做法是Agent機制:為每一個(gè)應用服務(wù)器安裝一個(gè)agent,就可以將SSO功能應用到這個(gè)應用服務(wù)器中的所有應用。
-
當前的方案不能支持分別位于不同domain的Web應用進(jìn)行SSO。這是因為瀏覽器在訪(fǎng)問(wèn)Web服務(wù)器的時(shí)候,僅僅會(huì )帶上和當前web服務(wù)器具有相同domain名稱(chēng)的那些cookie。要提供跨域的SSO的解決方案有很多其他的方法,在這里就不多說(shuō)了。Sun的Access Manager就具有跨域的SSO的功能。
-
另外,Filter的性能問(wèn)題也是需要重視的方面。因為Filter會(huì )截獲每一個(gè)符合URL映射規則的請求,獲得cookie,驗證其有效性。這一系列任務(wù)是比較消耗資源的,特別是驗證cookie有效性是一個(gè)遠程的http的調用,來(lái)訪(fǎng)問(wèn)SSOAuth的認證服務(wù),有一定的延時(shí)。因此在性能上需要做進(jìn)一步的提高。例如在本樣例中,如果將URL映射從“.jsp”改成“/*”,也就是說(shuō)filter對所有的請求都起作用,整個(gè)應用會(huì )變得非常慢。這是因為,頁(yè)面當中包含了各種靜態(tài)元素如gif圖片,css樣式文件,和其他html靜態(tài)頁(yè)面,這些頁(yè)面的訪(fǎng)問(wèn)都要通過(guò)filter去驗證。而事實(shí)上,這些靜態(tài)元素沒(méi)有什么安全上的需求,應該在filter中進(jìn)行判斷,不去效驗這些請求,性能會(huì )好很多。另外,如果在filter中加上一定的cache,而不需要每一個(gè)cookie效驗請求都去遠端的身份認證服務(wù)中執行,性能也能大幅度提高。
-
另外系統還需要很多其他的服務(wù),如在內存中定時(shí)刪除無(wú)用的cookie映射等等,都是一個(gè)嚴肅的解決方案需要考慮的問(wèn)題。
聯(lián)系客服