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