主要介紹CAS SSO的認證流程。有關這方面的內容再網上也有很多資料,寫這篇總結目的一來是自己在理解這塊內容的時候覺得專業(yè)術語有點多,理來理去有點繞,想通過總結來加強自己對這一塊內容的理解;其次,網上的資料雖然很多,但是很多都不是很全面,而這篇總結剛好可以在前人的基礎上進行過總結,結合網上的一些資料,再加上自己的 一些理解,應該可以更快的掌握這一塊內容。這篇總結主要是一些理論層面的內容,請看其它兩篇文章。
相關鏈接:
http://blog.csdn.net/gzseehope/article/details/72914188
http://blog.csdn.net/tyt_venice/article/details/56675186
http://blog.csdn.net/javaloveiphone/article/details/52439613
http://www.imooc.com/article/3558
運行原理
基本概念
- CAS ( Central Authentication Service ) 是 Yale 大學發(fā)起的一個企業(yè)級的、開源的項目,旨在為 Web 應用系統(tǒng)提供一種可靠的單點登錄解決方法(屬于 Web SSO)。CAS 開始于 2001 年, 并在 2004 年 12 月正式成為 JA-SIG 的一個項目。
- SSO 單點登錄(Single Sign-On)是目前比較流行的服務于企業(yè)業(yè)務整合的解決方案之一, SSO 使得在多個應用系統(tǒng)中,用戶只需要 登錄一次 就可以訪問所有相互信任的應用系統(tǒng)。
- CAS 包括兩部分: CAS Server 和 CAS Client 。CAS Server 負責完成對用戶的認證工作 , 需要獨立部署 , CAS Server 會處理用戶名 / 密碼等憑證(Credentials) 。CAS Client與受保護的客戶端應用部署在一起,以 Filter 方式保護受保護的資源。負責處理對客戶端受保護資源的訪問請求,需要對請求方進行身份認證時,重定向到 CAS Server 進行認證。(原則上,客戶端應用不再接受任何的用戶名密碼等 Credentials)。
CAS基礎模式
先看看網上的一個CAS基本協議過程:
image.png
- 訪問服務:SSO 客戶端發(fā)送請求訪問應用系統(tǒng)提供的服務資源。
- 定向認證:SSO 客戶端會重定向用戶請求到 SSO 服務器。
- 用戶認證:用戶身份認證。
- 發(fā)放票據: SSO 服務器會產生一個隨機的 Service Ticket 。
- 驗證票據: SSO 服務器驗證票據 ST 的合法性,驗證通過后,允許客戶端訪問服務。
- 傳輸用戶信息: SSO 服務器驗證票據通過后,傳輸用戶認證結果信息給客戶端。
CAS Client 與受保護的客戶端應用部署在一起,以 Filter 方式保護 Web 應用的受保護資源,過濾從客戶端過來的每一個 Web 請求,同時, CAS Client 會分析 HTTP 請求中是否包含請求 Service Ticket( ST 上圖中的 Ticket) ,如果沒有,則說明該用戶是沒有經過認證的;于是 CAS Client 會重定向用戶請求到 CAS Server ( Step 2 ),并傳遞 Service (要訪問的目的資源地址)。 Step 3 是用戶認證過程,如果用戶提供了正確的 Credentials , CAS Server 隨機產生一個相當長度、唯一、不可偽造的 Service Ticket ,并緩存以待將來驗證,并且重定向用戶到 Service 所在地址(附帶剛才產生的 Service Ticket ) , 并為客戶端瀏覽器設置一個 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新產生的 Ticket 過后,在 Step 5 和 Step6 中與 CAS Server 進行身份核實,以確保 Service Ticket 的合法性。在該協議中,所有與 CAS Server 的交互均采用 SSL 協議,以確保 ST 和 TGC 的安全性。協議工作過程中會有 2 次重定向 的過程。但是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對于用戶是透明的(使用 HttpsURLConnection )。
應用系統(tǒng)將登錄請求轉給認證中心,這個很好解決,我們一個HTTP重定向即可實現?,F在的問題是,用戶在認證中心登錄后,認證中心如何將消息轉回給該系統(tǒng)?這是在單web系統(tǒng)中不存在的問題。我們知道HTTP協議傳遞消息只能通過請求參數方式或cookie方式,cookie跨域問題不能解決,我們只能通過URL請求參數。我們可以將認證通過消息做成一個令牌(token)再利用HTTP重定向傳遞給應用系統(tǒng)。但現在的關鍵是:該系統(tǒng)如何判斷這個令牌的真?zhèn)??如果判斷這個令牌確實是由認證中心發(fā)出的,且是有效的?我們還需要應用系統(tǒng)和認證中心之間再來個直接通信,來驗證這個令牌確實是認證中心發(fā)出的,且是有效的。由于應用系統(tǒng)和認證中心是屬于服務端之間的通信,不經過用戶瀏覽器,相對是安全的。
假如現在應用集群中又兩個系統(tǒng)A、B。當客戶首次登錄A系統(tǒng)的時候,流程如下:
- 用戶瀏覽器訪問系統(tǒng)A需登錄受限資源。
- 系統(tǒng)A發(fā)現該請求需要登錄,將請求重定向到認證中心,進行登錄。
- 認證中心呈現登錄頁面,用戶登錄,登錄成功后,認證中心重定向請求到系統(tǒng)A,并附上認證通過令牌。
- 系統(tǒng)A與認證中心通信,驗證令牌有效,證明用戶已登錄。
- 系統(tǒng)A將受限資源返給用戶。
image.png
已登錄用戶首次訪問應用群中系統(tǒng)B時:
- 瀏覽器訪問另一應用B需登錄受限資源。
- 系統(tǒng)B發(fā)現該請求需要登錄,將請求重定向到認證中心,進行登錄。
- 認證中心發(fā)現已經登錄,即重定向請求響應到系統(tǒng)B,附帶上認證令牌。
- 系統(tǒng)B與認證中心通信,驗證令牌有效,證明用戶已登錄。
- 系統(tǒng)B將受限資源返回給客戶端。
image.png
登錄狀態(tài)判斷
用戶到認證中心登錄后,用戶和認證中心之間建立起了會話,我們把這個會話稱為全局會話。當用戶后續(xù)訪問系統(tǒng)應用時,我們不可能每次應用請求都到認證中心去判定是否登錄,這樣效率非常低下,這也是單Web應用不需要考慮的。我們可以在系統(tǒng)應用和用戶瀏覽器之間建立起局部會話,局部會話保持了客戶端與該系統(tǒng)應用的登錄狀態(tài),局部會話依附于全局會話存在,全局會話消失,局部會話必須消失。用戶訪問應用時,首先判斷局部會話是否存在,如存在,即認為是登錄狀態(tài),無需再到認證中心去判斷。如不存在,就重定向到認證中心判斷全局會話是否存在,如存在,按1提到的方式通知該應用,該應用與客戶端就建立起它們之間局部會話,下次請求該應用,就不去認證中心驗證了。
登出
用戶在一個系統(tǒng)登出了,訪問其它子系統(tǒng),也應該是登出狀態(tài)。要想做到這一點,應用除結束本地局部會話外,還應該通知認證中心該用戶登出。認證中心接到登出通知,即可結束全局會話,同時需要通知所有已建立局部會話的子系統(tǒng),將它們的局部會話銷毀。這樣,用戶訪問其它應用時,都顯示已登出狀態(tài)。
整個登出流程如下:
1.客戶端向應用A發(fā)送登出Logout請求。
2.應用A取消本地會話,同時通知認證中心,用戶已登出。
3.應用A返回客戶端登出請求。
4.認證中心通知所有用戶登錄訪問的應用,用戶已登出。
基礎模式總結
雖然很多東西都是從網上查過來的,但是對我們理解這塊的內容卻非常有幫助。當然,通過上面的內容,基本上額可以了解CAS SSO的基本原理,但還是有一些不明白的地方,因此還是有必要用自己的話總結一遍。
幾個注意點:
- 用戶認證操作是 完全交給認證中心的。比如用戶在瀏覽器上輸入的用戶名、密碼,這些信息按理來說是完全可以不和你的應用系統(tǒng)打交道的,因為認證操作完全是由認證中心(也就是 CAS Server)來處理的。
- 全局會話是是由用戶和認證中心建立的,這個全局會話就是TGT(Ticket Granting Ticket),或者說:登錄成功后,認證中心會產生一個票據叫TGT(Ticket Granting Ticket),TGT即代表了用戶與認證中心直接的全局會話,TGT存在,表明該用戶處于登錄狀態(tài)。TGT位于CAS服務器端,TGT并沒有放在Session中,也就是說,CAS全局會話的實現并沒有直接使用Session機制,而是利用了Cookie自己實現的,這個Cookie叫做TGC(Ticket Granting Cookie),它存放了TGT的id,保存在用戶瀏覽器上,CAS Server實現了TGT。
- 局部會話是由瀏覽器和應用系統(tǒng)之間建立的。并且局部會話必須依賴于全局會話。創(chuàng)建局部會話,相當于是在瀏覽器和因夠用系統(tǒng)之間創(chuàng)建了一個session。一般是在用戶首次登錄的時候創(chuàng)建的(是這樣嗎?)。
假如現在有A、B兩個系統(tǒng)屬于同一應用群,當用戶向系統(tǒng)A發(fā)送一個請求的時候,大概是這樣一個過程:
1、 用戶通過瀏覽器訪問系統(tǒng)A,系統(tǒng)A(也可以稱為CAS客戶端)去Cookie中拿JSESSION,即在Cookie中維護的當前回話session的id,如果拿到了,說明用戶已經登錄,這時候直接返回請求的資源,這里沒有啥好討論的。如果未拿到,說明用戶未登錄,主要討論這一塊內容。
2、 如果發(fā)現用戶未登錄。系統(tǒng)A就重定向到認證中心(CAS 服務端),并將當前客戶端的地址作為參數傳遞到認證中心(以便于認證中心生成ST之后返回當前地址),假如系統(tǒng)A的地址為http://a:8080/,CAS認證中心的服務地址為http://cas.server:8080/,那么重點向前后地址變化為:由http://a:8080/到http://cas.server:8080/?service=http://a:8080/,由此可知,重點向到認證中心,認證中心拿到了當前訪問客戶端的地址。
3、 重定向到認證中心后,認證中心首先會去瀏覽器拿全局會話id(也就是TGT,TGC中存放了TGT的id),如果不存在就進入認證中心的登錄界面,用戶輸入用戶名、密碼進行登錄,登錄成功后,CAS 服務端創(chuàng)建全局會話TGT,并且將TGT最為緩存在cookie中(TGC),同時會產生一個隨機的 Service Ticket (ST),并且重定向到A系統(tǒng),并且攜帶ST,重定向之后的地址變?yōu)椋?a href="http://a:8080/?ticket=ST-XXXX-XXX" target="_blank" rel="nofollow">http://a:8080/?ticket=ST-XXXX-XXX。
4、 系統(tǒng)A通過地址欄獲取ticket的參數值ST票據,然后從后臺將ST發(fā)送給CAS server認證中心驗證,驗證ST有效后,CAS server返回當前用戶登錄的相關信息,系統(tǒng)A接收到返回的用戶信息,并為該用戶創(chuàng)建session會話(局部會話),會話id由cookie維護,來證明其已登錄。
5、 系統(tǒng)A將資源返回給 瀏覽器。
至此,用戶向系統(tǒng)A發(fā)送一個請求的流程已經完成。這個時候,如果用戶向系統(tǒng)A發(fā)送另外一個請求,因為系統(tǒng)A和瀏覽器之間已經創(chuàng)建了一個局部會話,因此這時候會直接返回資源。
假如還是同一個用戶,向系統(tǒng)B發(fā)送一個請求,流程是什么樣的?
1、 在系統(tǒng)A登錄成功后,用戶和認證中心之間建立起了全局會話,這個全局會話就是TGT(Ticket Granting Ticket),TGT位于CAS服務器端,TGT并沒有放在Session中,也就是說,CAS全局會話的實現并沒有直接使用Session機制,而是利用了Cookie自己實現的,這個Cookie叫做TGC(Ticket Granting Cookie),它存放了TGT的id,保存在用戶瀏覽器上。用戶發(fā)送登錄系統(tǒng)B的請求,首先會去Cookie中拿JSESSION,因為系統(tǒng)B并未登錄過,session會話還未創(chuàng)建,JSESSION的值是拿不到的,然后將請求重定向到CAS認證中心,CAS認證中心先去用戶瀏覽器中拿TGC的值,也就是全局會話id,如果存在則代表用戶在認證中心已經登錄,附帶上認證令牌重定向到系統(tǒng)B。如果全局會話不存在,那這是就好執(zhí)行一次建立全局會話的過程。
2、 系統(tǒng)B拿到ST后,也會在系統(tǒng)B和瀏覽器之間建立一個局部會話。
3、 將系統(tǒng)B的資源返回給瀏覽器。
認證中心清除當前用戶的全局會話TGT,同時清掉cookie中TGT的id:TGC;
然后是各個客戶端系統(tǒng),比如系統(tǒng)A、系統(tǒng)B,清除局部會話session,同時清掉cookie中session會話id:jsession。
CAS代理模式
用網上找到的一些概念:代理模式形式為用戶訪問App1,App1又依賴于App2來獲取一些信息,如:User -->App1 -->App2 。這種情況下,假設App2也是需要對User進行身份驗證才能訪問,那么,為了不影響用戶體驗(過多的重定向導致User的IE窗口不停地閃動),CAS引入了一種Proxy認證機制,即CAS Client可以代理用戶去訪問其它Web應用。代理的前提是需要CAS Client擁有用戶的身份信息(類似憑據)。之前我們提到的TGC是用戶持有對自己身份信息的一種憑據,這里的PGT就是CAS Client端持有的對用戶身份信息的一種憑據。憑借TGC,User可以免去輸入密碼以獲取訪問其它服務的Service Ticket,所以,這里憑借PGT,Web應用可以代理用戶去實現后端的認證,而無需前端用戶的參與。下面為代理應用(helloService)獲取PGT的過程:(注:PGTURL用于表示一個Proxy服務,是一個回調鏈接;PGT相當于代理證;PGTIOU為取代理證的鑰匙,用來與PGT做關聯關系;)
image.png
CAS Client 在基礎協議之上,在驗證ST時提供了一個額外的PGT URL(而且是 SSL 的入口)給CAS Server,使得CAS Server可以通過PGT URL提供一個PGT給CAS Client。
CAS Client拿到了PGT(PGTIOU-85…..ti2td),就可以通過PGT向后端Web應用進行認證。
下面是代理認證和提供服務的過程:
image.png
與基礎模式的異同
首先要明白一點:基礎模式是用戶對自己信息的一種憑據;而代理模式是CAS Client對用戶信息的一種憑據。代理模式認證與普通模式的認證其實差別不大,第一步和第二步與基礎模式的第一步和第二步幾乎一樣,唯一不同的是,代理模式用的是PGT而不是TGC,是Proxy Ticket(PT)而不是Service Ticket(ST)。
CAS Server
主要介紹CAS Server的簡單搭建過程。感覺在實際開發(fā)過程中,應該更多的是處理一個CAS Client的集成問題吧?但也還是簡單的了解一下CAS Server的搭建過程吧。
相關鏈接:
http://www.cnblogs.com/rwxwsblog/p/4954795.html
http://www.lxweimin.com/p/67d10532c5f8
基礎環(huán)境
- 系統(tǒng):win10
- JDK: jdk1.8.0_101
- CAS Server:cas-server-4.0.0-release.zip
前提條件,JDK和tomcat已經配置成功。
生成證書
keytool -genkey -alias castest -keyalg RSA -keystore D:/keys/castest
生成一個別名為castest的證書。
此處需要特別注意口令(后續(xù)導入導出證書、CAS服務器端均要用到此口類)和“名字與姓氏”(為CAS跳轉域名,否則會報錯)
image.png
導出證書
keytool -export -file F:/keys/castest.crt -alias castest -keystore D:/keys/castest
將證書導入到客戶端JRE
將證書導入到客戶端JRE中(注意、是導入JRE中),如果security中已經存在cacerts,需要先將其刪除。
keytool -import -keystore "C:\Program Files\Java\jdk1.8.0_101\jre\lib\security\cacerts" -file D:/keys/castest.crt -alias castest
這里由一個需要注意的地方,因為我的JDK是安裝在C盤(系統(tǒng)盤),而你往系統(tǒng)盤里面寫入文件是需要權限的,所以,這條命令可能需要用管理員權限運行。
如果看過如下錯誤,請使用管理員權限運行:
image.png
以管理員權限運行結果如下:
image.png
配置服務器端
- 從http://developer.jasig.org/cas/上下載cas服務器端cas-server-4.0.0-release.zip,在modules目錄下找到cas-server-webapp-4.0.0.war,將其復制到%TOMCAT_HOME%\webapps下,并將名稱改為cas.war
image.png
- 修改%TOMCAT_HOME%\conf\server.xml文件:
image.png
- 啟動tomcat測試
進入tomcat根目錄下的bin目錄,執(zhí)行:startup.bat
提示啟動成功,這時候打開瀏覽器,輸入地址:https://localhost:8443,提示
image.png
點擊繼續(xù)訪問,可以看到tomcat的主頁:
image.png
說明證書安裝成功。
用戶密碼通過配置文件配置
在瀏覽器輸入地址:https://localhost:8443/cas
這時瀏覽器會重定向到https://localhost:8443/cas/login
image.png
這時候需要我們提供用戶名和密碼,可是用戶名和密碼我們并不知道。不過它默認的用戶名密碼是配置在配置文件中。
進入目錄:tomcat根目錄 \webapps\cas\WEB-INF,打deployerConfigContext.xml文件,找到以下內容:
image.png
這里的key就是用戶名,value就是密碼,我們也可以對其自己修改。這時候回到瀏覽器,輸入用戶名:casuser和密碼:Mellon。提示登錄成功
image.png
至此,cas服務端配置成功。
用戶密碼和數據庫交互
假如需要和數據庫交互,應該如何配置呢?因為我本地使用的是mysql,所以需要將以下幾個jar包放到WEB-INF/lib目錄下:
mysql-connector-java-5.1.34.jar
c3p0-0.9.1.1.jar
cas-server-support-jdbc-4.0.0.jar
其中的cas-server-support-jdbc-4.0.0.jar我是直接從module目錄下拷貝過來的,我們這里 是直接拷貝到該目錄下,也有其他的方法,因為cas server本來就一個maven項目,也可以自己修改pom.xml文件,然后重新打包就好了。
image.png
修改配置文件,支持mysql交互。進入目錄:tomcat根目錄 \webapps\cas\WEB-INF,打deployerConfigContext.xml文件,找到以下內容:
image.png
將這一段修改成以下內容:
image.png
然后,在該配置文件中添加兩個bean配置,如下:
image.png
至此,配置文件已經完成,接下來是測試。
測試mysql交互
重啟tomcat。如果這時候提示你登錄成功,則在瀏覽器地址欄輸入:
https://localhost:8443/cas/logout,這是登出
image.png
然后重新在地址欄輸入:https://localhost:8443/cas/
這時候會提示需要重新登錄,這時候我們不使用配置文件中的用戶名和密碼,而是使用數據庫中的用戶名密碼。為了測試方便,此處我選擇了一個簡單的數據庫進行測試,密碼也沒有進行加密處理:
image.png
可以發(fā)現,在 o_user表中,對應了一條 Moxie 和 Admin的記錄,這時候在登錄界面輸入用戶名Moxie密碼Admin:
image.png
點擊登錄之后,成功跳轉到登錄成功界面,說明和mysql交互成功。當然這里的交互只是最簡單的交互,還需要慢慢摸索。
CAS Client
主要介紹CAS Client的簡單搭建過程,多個客戶端實現單點登錄。相關鏈接:
http://www.cnblogs.com/rwxwsblog/p/4954795.html
http://www.lxweimin.com/p/67d10532c5f8
http://blog.csdn.net/zhmz1326/article/details/52279740
基本環(huán)境
- 系統(tǒng):win10
- JDK: jdk1.8.0_101
- Tomcat:tomcat8
- CAS Server:cas-server-4.0.0-release.zip
準備tomcat
為了演示多個客戶端的單點登錄功能,所以客戶端至少需要兩個。所以本地只使用2個tomcat客戶端來測試。首先,客戶端應用是要和CAS服務端進行交互的,所以這里需要jar文件,放在客戶端應用的lib目錄下。分別是:cas-client-core-3.2.1.jar、commons-logging.jar。這里直接使用tomcat默認自帶的 webapps\examples 作為演示的簡單web項目。
目錄結構如下:
image.png
客戶端添加jar包
Tomcat解壓之后,現在還無法同CAS Server交互,還缺少兩個jar包,分別是:
cas-client-core-3.2.0.jar、
commons-logging-1.1.jar
可以從:http://developer.jasig.org/cas-clients/下載。(注意,這里版本不對可能會出異常,比如我一開始用的是3.33版本,在配置filter之后就發(fā)現不能正常訪問)。
需要放到客戶端應用的lib目錄下面。我這里的目錄分別是:
D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client1\webapps\examples\WEB-INF\lib
和
D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client2\webapps\examples\WEB-INF\lib
image.png
客戶端添修改端口
因為我們是在同一臺機器上運行,然后tomcat默認的端口是8080,如果不修改tomcat端口,在啟動CAS Server之后,其他的兩個tomcat 客戶端就無法啟動了,因為端口被占用了,所以這里需要修改兩個tommcat 客戶端的端口 。
修改client1端口:
編輯D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client1\conf\server.xml文件,找到如下內容:
image.png
將其中的8080端口和8009端口修改成其它 4位數字的端口號
我這里將其修改為:18080 和 18009。即,如下:
image.png
修改client2端口:
編輯D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client2\conf\server.xml文件,找到如下內容:
image.png
將其中的8080端口和8009端口修改成:28080 和 28009
image.png
然后啟動發(fā)現啟動不了,網上查了一下資料,發(fā)現還有一個地方需要修改:
image.png
將兩個tomcat客戶端的該端口分別改成: 8006 和 8007。
配置host
網上說:因為CAS單點登錄系統(tǒng)是基于JAVA安全證書的 https 訪問, 要使用CAS單點登錄必須要配置域名, cas是不能通過ip訪問的。首先cas只能通過域名來訪問,不能通過ip訪問,同時cas Server是生成證書yiduan,所以要求比較嚴格,所以如果不這么做的話,及時最終按照教程配置完成,cas也可以正常訪問,訪問一個客戶端應用雖然能進入cas驗證首頁,但是,當輸入信息正確后,cas在回調轉入你想訪問的客戶端應用的時候,會出現No subject alternative names present錯誤異常信息,這個錯誤也就是在上面輸入的第一個問題答案不是域名導致、或者與hosts文件配置的不一致導致。
不是理解的很好,所以這里還是配置一下host吧。下面3個ip都是127.0.0.1,因為環(huán)境都是在同一臺機器,所以ip都是一致的,我們再把不同的服務端和客戶端應用,使用不同域名加以區(qū)分。一個域名對應一個應用,模擬多端
image.png
sso.castest..com =>> 對應部署cas server的tomcat,這個虛擬域名還用于服務端證書生成
client1.castest..com =>> 對應部署client1客戶端應用的tomcat
client2.castest..com =>> 對應部署client2客戶端應用的tomcat
啟動tomcat測試
這里的測試僅僅是測試兩個tomcat客戶端是否能夠成功啟動,并不涉及到和CAS Server相關的內容。
分別進入:D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client1\bin 和
D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client2\bin ,并執(zhí)行 startup.bat
在瀏覽器分地址欄分別輸入:
http://client1.castest.com:18080/examples/servlets/
http://client2.castest.com:28080/examples/servlets/
可以看到如下界面,說明tomcat客戶端啟動成功:
image.png
客戶端和CAS Server連接
配置 client1:
編輯 D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client1\webapps\examples\WEB-INF\web.xml 配置文件,添加如下內容
<!-- ======================== 單點登錄開始 ======================== -->
<!-- 用于單點退出,該過濾器用于實現單點登出功能,可選配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 該過濾器用于實現單點登出功能,可選配置。 -->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 該過濾器負責用戶的認證工作,必須啟用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://sso.castest.com:8443/cas/login</param-value>
<!--這里的server是服務端的IP-->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://client1.castest.com:18080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 該過濾器負責對Ticket的校驗工作,必須啟用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://sso.castest.com:8443/cas/</param-value><!-- 此處必須為登錄url /cas/,帶有任何其它路徑都會報錯,如“https://sso.castest.com:8443/cas/login”,這樣也會報錯。 -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://client1.castest.com:18080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
該過濾器負責實現HttpServletRequest請求的包裹,
比如允許開發(fā)者通過HttpServletRequest的getRemoteUser()方法獲得SSO登錄用戶的登錄名,可選配置。
-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
該過濾器使得開發(fā)者可以通過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登錄名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ======================== 單點登錄結束 ======================== -->
配置 client2:
編輯 D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64-client2\webapps\examples\WEB-INF\web.xml 配置文件,添加如下內容
<!-- ======================== 單點登錄開始 ======================== -->
<!-- 用于單點退出,該過濾器用于實現單點登出功能,可選配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- 該過濾器用于實現單點登出功能,可選配置。 -->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 該過濾器負責用戶的認證工作,必須啟用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://sso.castest.com:8443/cas/login</param-value>
<!--這里的server是服務端的IP-->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://client2.castest.com:28080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 該過濾器負責對Ticket的校驗工作,必須啟用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://sso.castest.com:8443/cas/</param-value><!-- 此處必須為登錄url /cas/,帶有任何其它路徑都會報錯,如“https://sso.castest.com:8443/cas/login”,這樣也會報錯。 -->
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://client2.castest.com:28080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
該過濾器負責實現HttpServletRequest請求的包裹,
比如允許開發(fā)者通過HttpServletRequest的getRemoteUser()方法獲得SSO登錄用戶的登錄名,可選配置。
-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
該過濾器使得開發(fā)者可以通過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登錄名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ======================== 單點登錄結束 ======================== -->
測試SSO功能
至此,所以配置都已經配置完成。這時候需要測試單點登錄功能:
首先,重啟 tomcat client 和 tomcat client2。也就是說,在這個時候,我們的三個tomcat都已經啟動成功,分別為:CAS Server 、 client1 、client2。
然后,訪問:http://client1.castest.com:18080/examples/servlets/servlet/HelloWorldExample
這時候,會發(fā)現瀏覽器地址重定向到了:
image.png
不過后面那一串是什么東西?利用在線工具對這個地址解碼,得到以下地址:
https://sso.castest.com:8443/cas/login?service=http://client1.castest.com:18080/examples/servlets/servlet/HelloWorldExample
是不是很熟悉,現在這個過程,是不是很像 “CAS SSO 原理”里面的內容呢?也就是說,在我們訪問client1的時候,按CAS 認證流程走了一遍。后面那個參數:service=xxx,就是我們認證成功之后重定向的地址,也就是我們當前訪問的地址。
這時候輸入用戶名Moxie 密碼 Admin,發(fā)現界面出現了 我想想要訪問的資源,同時,觀察瀏覽器地址欄的變化:
image.png
多了一個 ;jsessionid=EFDD121B137669268985F413E745891C
這時候我們再訪問 client2,因我我們剛剛已經登錄過一次了,所以,這次在我們訪問client2的時候,按理來說是不需要重新登錄的,下面進行測試:
在瀏覽器地址欄輸入:
http://client2.castest.com:28080/examples/servlets/servlet/HelloWorldExample
然而,效果卻并不是我們預期的那樣,竟然出現以下界面:
image.png
當我們點擊here的時候,出現以下錯誤:
image.png
這是什么原因?經過一番google,發(fā)現原來是:CAS服務器的ST票據有效期時間太短,默認是10秒。開發(fā)在debug時,非常容易超過10秒,所以會發(fā)生TicketValidationException異常。
首先,設置CAS Server的超時時間:
編輯D:\GreenSoft\Apach\Tomcat\Tomcat-8.0.30-x64\webapps\cas\WEB-INF\cas.properties
設置有效時間為: 3600
image.png
重啟CAS Server, 并且登出。
然后訪問
http://client1.castest.com:18080/examples/servlets/servlet/HelloWorldExample
提示輸入用戶名密碼,我們輸入 Moxie Admin 進行登錄,出現以下界面;
image.png
說明client1沒有問題。
然后訪問client2
http://client2.castest.com:28080/examples/servlets/servlet/HelloWorldExample
同樣出現以下界面
image.png
至此,單點配置成功。
繼承SpringSecurity
spring-security集成,主要介紹spring-security-cas的使用。
配置文件
根據上面的幾篇文章,可以知道CAS分為Client端和Server端,這里主要講的是Client端。
casSecurity.xml
<!-- 此文件用于CAS sso登錄方式 -->
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<!-- CAS 基本屬性配置-->
<beans:bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties">
<beans:property name="service"
value="${cas.service}"/>
<beans:property name="sendRenew" value="false"/>
</beans:bean>
<!-- CAS 認證配置 -->
<http entry-point-ref="casEntryPoint" access-decision-manager-ref="accessDecisionManager">
<csrf request-matcher-ref="csrfCasSecurityRequestMatcher"/>
<intercept-url pattern="/resources/**" access="permitAll"/>
<intercept-url pattern="/lib/**" access="permitAll"/>
<!-- <intercept-url pattern="/common/**" access="permitAll"/>-->
<!-- <intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/login.html" access="permitAll" />
<intercept-url pattern="/verifiCode" access="permitAll" />-->
<intercept-url pattern="/timeout" access="permitAll"/>
<intercept-url pattern="/websocket/**" access="permitAll"/>
<intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<!-- <access-denied-handler error-page="/access-denied"/>
<session-management invalid-session-url="/timeout"/>-->
<!--<form-login login-page='/login' authentication-success-handler-ref="successHandler"
authentication-failure-handler-ref="loginFailureHandler"/>-->
<!--authentication-failure-url="/login?error=true"/>-->
<!-- 驗證碼攔截器 -->
<!-- <custom-filter ref="captchaVerifierFilter" before="FORM_LOGIN_FILTER"/>-->
<custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/>
<custom-filter position="CAS_FILTER" ref="casFilter"/>
<custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/>
<headers defaults-disabled="true">
<cache-control/>
</headers>
</http>
<!-- 認證管理器,確定用戶,角色及相應的權限 -->
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
<!-- 投票器 -->
<beans:constructor-arg>
<beans:list>
<beans:bean class="com.hand.hap.security.PermissionVoter"/>
<beans:bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
<beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<!-- CAS Filter 配置 -->
<beans:bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationSuccessHandler" ref="successHandler"/>
</beans:bean>
<beans:bean id="successHandler" class="com.hand.hap.security.CustomAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/index"/>
</beans:bean>
<beans:bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<beans:property name="loginUrl" value="${cas.ssoserver.loginurl}"/>
<beans:property name="serviceProperties" ref="serviceProperties"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="casAuthenticationProvider"/>
<!-- <authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="passwordManager"/>
</authentication-provider>-->
</authentication-manager>
<beans:bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<beans:property name="serviceProperties" ref="serviceProperties"/>
<beans:property name="authenticationUserDetailsService" ref="customAuthenticationUserDetailsService"/>
<beans:property name="ticketValidator">
<beans:bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<beans:constructor-arg index="0" value="${cas.ssoserver.url}"/>
</beans:bean>
</beans:property>
<beans:property name="key" value="an_id_for_this_auth_provider_only"/>
</beans:bean>
<beans:bean id="customAuthenticationUserDetailsService"
class="com.hand.hap.security.CustomAuthenticationUserDetailsService">
</beans:bean>
<beans:bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/>
<!-- This filter redirects to the CAS Server to signal Single Logout should be performed -->
<beans:bean id="requestSingleLogoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<beans:constructor-arg value="${cas.ssoserver.logouturl}"/>
<beans:constructor-arg>
<beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
</beans:constructor-arg>
<beans:property name="filterProcessesUrl" value="/logout"/>
</beans:bean>
<beans:bean id="csrfCasSecurityRequestMatcher" class="com.hand.hap.security.CsrfSecurityRequestMatcher">
<beans:property name="excludeUrls">
<beans:list>
<beans:value>/login</beans:value>
<beans:value>/websocket/**</beans:value>
<beans:value>/ureport/**</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<!-- <beans:bean id="captchaVerifierFilter" class="com.hand.hap.security.CaptchaVerifierFilter">
<beans:property name="captchaField" value="verifiCode"/>
</beans:bean>
<beans:bean id="loginFailureHandler" class="com.hand.hap.security.LoginFailureHandler"/>-->
</beans:beans>
config.properties
cas.service=http://localhost:8080/hap/login/cas
cas.ssoserver.loginurl=https://localhost:8088/sso/login
cas.ssoserver.url=https://localhost:8088/sso
cas.ssoserver.logouturl=https://localhost:8088/sso/logout?service=http://localhost:8080/hap
在Spring Security中,通過設置entry-point-ref="第三方登錄入口",可以在訪問系統(tǒng)首頁的時候進行登錄跳轉。它在系統(tǒng)進行登錄認證的過程會進行認證,認證不通過則拋出一個異常給ExceptionTranslationFilter,由它進行通過entry-point-ref設置的入口點進行處理。
<custom-filter position="CAS_FILTER" ref="casFilter"/>
position="CAS_FILTER"就表示將定義的 Filter 放在 CAS_FILTER 對應的那個位置,這里對應的是 casFilter
<!-- CAS Filter 配置 -->
<beans:bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="authenticationSuccessHandler" ref="successHandler"/>
</beans:bean>
<!-- 登錄成功處理 -->
<beans:bean id="successHandler" class="com.hand.hap.security.CustomAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/index"/>
</beans:bean>
其實在登錄時的權限校驗和之前的一樣,區(qū)別在于
- AuthenticationProvider 用的是 CasAuthenticationProvider
- 使用 entry-point-ref="casEntryPoint" 來處理登錄失敗時的一些邏輯
CasAuthenticationEntryPoint 中的 commence 方法如下,重定向到 redirectUrl,這些就是我們在使用 spring-security-cas 需要的配置信息:
public final void commence(final HttpServletRequest servletRequest,
final HttpServletResponse response,
final AuthenticationException authenticationException) throws IOException,
ServletException {
final String urlEncodedService = createServiceUrl(servletRequest, response);
final String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
response.sendRedirect(redirectUrl);
}