建立使用 Cas 進(jìn)行單點登錄的應(yīng)用
?
目錄
1.1 加入 cas-client-core-xxx.jar 到 classpath
1.2 配置 Filter
1.2.1 AuthenticationFilter
1.2.2 TicketValidationFilter
1.2.3 HttpServletRequestWrapperFilter
1.2.4 AssertionThreadLocalFilter
1.2.5 基于 Spring 的 Filter 配置
1.3 添加證書到信任庫
?
?????? 根據(jù)之前的描述我們知道, Cas 由兩部分組成, Cas Server 和 Cas Client 。 Cas Server 是 Cas 自己的服務(wù)端,而 Cas Client 是 Cas 客戶端,其需要與我們自己的應(yīng)用進(jìn)行集成。
?
1.1 ???? 加入 cas-client-core-xxx.jar 到 classpath
?????? 在我們下載的 Cas Client 壓縮包的 modules 目錄下可以找到一個名為 cas-client-core-xxx.jar 的 jar 文件,首先需要將該 jar 包加入我們應(yīng)用的類路徑下,筆者這里使用的是 cas-client-core-3.1.11.jar 。如果用戶的應(yīng)用是使用 Maven 構(gòu)造的,則可以在應(yīng)用的 pom.xml 文件中加入如下依賴。
?? < dependency >
????? < groupId > org.jasig.cas.client </ groupId >
????? < artifactId > cas -client-core </ artifactId >
????? < version > 3.1.11 </ version >
?? </ dependency >
?
1.2 ???? 配置 Filter
?????? 然后需要我們在應(yīng)用的 web.xml 文件中配置四個 Filter ,這四個 Filter 必須按照固定的順序來進(jìn)行配置,而且它們必須配置在應(yīng)用的其它 Filter 之前。它們的先后順序要求如下:
l ? AuthenticationFilter
l ? TicketValidationFilter
l ? HttpServletRequestWrapperFilter
l ? AssertionThreadLocalFilter
?
?????? 這些 Filter 有的必須指定某些參數(shù),有的可以指定某些參數(shù),這些參數(shù)可以通過 context-param 來指定,也可以通過 init-param 來指定。 Cas Client 默認(rèn)會先從 init-param 取,沒取到則從 context-param 取,所以當(dāng) init-param 和 context-param 都指定了某個參數(shù)時, init-param 指定的將擁有更高的優(yōu)先級。所以當(dāng)多個 Filter 需要共用一個參數(shù)時,我們可以把它定義為 context-param 。
?
1.2.1 ??? AuthenticationFilter
?????? AuthenticationFilter 用來攔截所有的請求,用以判斷用戶是否需要通過 Cas Server 進(jìn)行認(rèn)證,如果需要則將跳轉(zhuǎn)到 Cas Server 的登錄頁面。如果不需要進(jìn)行登錄認(rèn)證,則請求會繼續(xù)往下執(zhí)行。
???? AuthenticationFilter 有兩個用戶必須指定的參數(shù),一個是用來指定 Cas Server 登錄地址的 casServerLoginUrl ,另一個是用來指定認(rèn)證成功后需要跳轉(zhuǎn)地址的 serverName 或 service 。 service 和 serverName 只需要指定一個就可以了。當(dāng)兩者都指定了,參數(shù) service 將具有更高的優(yōu)先級,即將以 service 指定的參數(shù)值為準(zhǔn)。 service 和 serverName 的區(qū)別在于 service 指定的是一個確定的 URL ,認(rèn)證成功后就會確切的跳轉(zhuǎn)到 service 指定的 URL ;而 serverName 則是用來指定主機名,其格式為 {protocol}:{hostName}:{port} ,如: https://localhost:8443 ,當(dāng)指定的是 serverName 時, AuthenticationFilter 將會把它附加上當(dāng)前請求的 URI ,以及對應(yīng)的查詢參數(shù)來構(gòu)造一個確定的 URL ,如指定 serverName 為“ http://localhost ”,而當(dāng)前請求的 URI 為“ /app ”,查詢參數(shù)為“ a=b&b=c ”,則對應(yīng)認(rèn)證成功后的跳轉(zhuǎn)地址將為“ http://localhost/app?a=b&b=c ”。
?
?????? 除了上述必須指定的參數(shù)外, AuthenticationFilter 還可以指定如下可選參數(shù):
l ? renew :當(dāng)指定 renew 為 true 時,在請 Cas Server 時將帶上參數(shù)“ renew=true ”,默認(rèn)為 false 。
l ? gateway :指定 gateway 為 true 時,在請求 Cas Server 時將帶上參數(shù)“ gateway=true ”,默認(rèn)為 false 。
l ? artifactParameterName :指定 ticket 對應(yīng)的請求參數(shù)名稱,默認(rèn)為 ticket 。
l ? serviceParameterName :指定 service 對應(yīng)的請求參數(shù)名稱,默認(rèn)為 service 。
?
?????? 如下是一個配置 AuthenticationFilter 的示例, serverName 由于在接下來配置的 Filter 中還要用,所以利用 context-param 將其配置為一個公用的參數(shù)。“ elim ”對應(yīng)我的電腦名。
?
?? < context-param >
????? < param-name > serverName </ param-name >
????? < param-value > http://elim:8080 </ param-value >
?? </ context-param >
??
?? < filter >
????? < filter-name > casAuthenticationFilter </ filter-name >
?? < filter-class > org.jasig.cas.client.authentication.AuthenticationFilter </ filter-class >
????? < init-param >
???????? < param-name > casServerLoginUrl </ param-name >
???????? < param-value > https://elim:8443/cas/login </ param-value >
????? </ init-param >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casAuthenticationFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
1.2.2 ??? TicketValidationFilter
?????? 在請求通過 AuthenticationFilter 的認(rèn)證之后,如果請求中攜帶了參數(shù) ticket 則將會由 TicketValidationFilter 來對攜帶的 ticket 進(jìn)行校驗。 TicketValidationFilter 只是對驗證 ticket 的這一類 Filter 的統(tǒng)稱,其并不對應(yīng) Cas Client 中的一個具體類型。 Cas Client 中有多種驗證 ticket 的 Filter ,都繼承自 AbstractTicketValidationFilter ,它們的驗證邏輯都是一致的,都有 AbstractTicketValidationFilter 實現(xiàn),所不同的是使用的 TicketValidator 不一樣。筆者這里將以 Cas10TicketValidationFilter 為例,其它還有 Cas20ProxyReceivingTicketValidationFilter 和 Saml11TicketValidationFilter 。
?
?? < filter >
????? < filter-name > casTicketValidationFilter </ filter-name >
?? < filter-class > org.jasig.cas.client.validation.Cas10TicketValidationFilter </ filter-class >
????? < init-param >
???????? < param-name > casServerUrlPrefix </ param-name >
???????? < param-value > https://elim:8443/cas </ param-value >
????? </ init-param >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casTicketValidationFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
?????? 必須指定的參數(shù):
l ? casServerUrlPrefix :用來指定 Cas Server 對應(yīng) URL 地址的前綴,如上面示例的“ https://elim:8443/cas ”。
l ? serverName 或 service :語義跟前面介紹的一致。
?
?????? 可選參數(shù):
l ? redirectAfterValidation ? :表示是否驗證通過后重新跳轉(zhuǎn)到該 URL ,但是不帶參數(shù) ticket ,默認(rèn)為 true 。
l ? useSession ? :在驗證 ticket 成功后會生成一個 Assertion 對象,如果 useSession 為 true ,則會將該對象存放到 Session 中。如果為 false ,則要求每次請求都需要攜帶 ticket 進(jìn)行驗證,顯然 useSession 為 false 跟 redirectAfterValidation 為 true 是沖突的。默認(rèn)為 true 。
l ? exceptionOnValidationFailure ? :表示 ticket 驗證失敗后是否需要拋出異常,默認(rèn)為 true 。
l ? renew :當(dāng)值為 true 時將發(fā)送“ renew=true ”到 Cas Server ,默認(rèn)為 false 。
?
1.2.3 ??? HttpServletRequestWrapperFilter
?????? HttpServletRequestWrapperFilter 用于將每一個請求對應(yīng)的 HttpServletRequest 封裝為其內(nèi)部定義的 CasHttpServletRequestWrapper ,該封裝類將利用之前保存在 Session 或 request 中的 Assertion 對象重寫 HttpServletRequest 的 getUserPrincipal() 、 getRemoteUser() 和 isUserInRole() 方法。這樣在我們的應(yīng)用中就可以非常方便的從 HttpServletRequest 中獲取到用戶的相關(guān)信息。以下是一個配置 HttpServletRequestWrapperFilter 的示例:
?? < filter >
????? < filter-name > casHttpServletRequestWrapperFilter </ filter-name >
?? < filter-class > org.jasig.cas.client.util.HttpServletRequestWrapperFilter </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casHttpServletRequestWrapperFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
1.2.4 ??? AssertionThreadLocalFilter
?????? AssertionThreadLocalFilter 是為了方便用戶在應(yīng)用的其它地方獲取 Assertion 對象,其會將當(dāng)前的 Assertion 對象存放到當(dāng)前的線程變量中,那么以后用戶在程序的任何地方都可以從線程變量中獲取當(dāng)前 Assertion ,無需再從 Session 或 request 中進(jìn)行解析。該線程變量是由 AssertionHolder 持有的,我們在獲取當(dāng)前的 Assertion 時也只需要通過 AssertionHolder 的 getAssertion() 方法獲取即可,如:
?? Assertion assertion = AssertionHolder. getAssertion ();
?
?????? 像 AssertionThreadLocalFilter 這種設(shè)計理念是非常好的,實際應(yīng)用中使用的也比較多, Spring Security 中也有用到這種理念。為了便于大家了解,特貼出 AssertionHolder 的源碼如下:
public? class AssertionHolder {
?
??? /**
???? * ThreadLocal to hold the Assertion for Threads to access.
???? */
??? private? static? final ThreadLocal threadLocal = new ThreadLocal();
?
?
??? /**
???? * Retrieve the assertion from the ThreadLocal.
???? */
??? public? static Assertion getAssertion() {
??????? return (Assertion) threadLocal .get();
??? }
?
??? /**
???? * Add the Assertion to the ThreadLocal.
???? */
??? public? static? void setAssertion( final Assertion assertion) {
??????? threadLocal .set(assertion);
??? }
?
??? /**
???? * Clear the ThreadLocal.
???? */
??? public? static? void clear() {
??????? threadLocal .set( null );
??? }
}
?
?????? 以下是配置 AssertionThreadLocalFilter 的示例:
?? < filter >
????? < filter-name > casAssertionThreadLocalFilter </ filter-name >
?? ? < filter-class > org.jasig.cas.client.util.AssertionThreadLocalFilter </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casAssertionThreadLocalFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
1.2.5 ??? 基于Spring的Filter配置
?????? 使用 Cas 單點登錄的應(yīng)用需要我們在應(yīng)用的 web.xml 文件中配置上述介紹的四個 Filter ,但如果用戶的應(yīng)用是使用 Spring 開發(fā)的,則我們可以只在 web.xml 文件中配置四個 Spring 的 DelegatingFilterProxy 用來代理需要配置的四個 Filter ,對應(yīng)的 Filter 名稱對應(yīng)我們需要代理的 Spring ApplicationContext 中 bean 的名稱,此時我們需要將對應(yīng)的 Filter 配置為 Spring ApplicationContext 中的一個 bean 對象。所以此時對應(yīng)的 web.xml 文件的定義應(yīng)該是這樣的:
?? < filter >
????? < filter-name > casAuthenticationFilter </ filter-name >
?? ? < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casAuthenticationFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
?? < filter >
????? < filter-name > casTicketValidationFilter </ filter-name >
?? ? < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casTicketValidationFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
?? < filter >
????? < filter-name > casHttpServletRequestWrapperFilter </ filter-name >
?? ? < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casHttpServletRequestWrapperFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
?? < filter >
????? < filter-name > casAssertionThreadLocalFilter </ filter-name >
?? ? < filter-class > org.springframework.web.filter.DelegatingFilterProxy </ filter-class >
?? </ filter >
?? < filter-mapping >
????? < filter-name > casAssertionThreadLocalFilter </ filter-name >
????? < url-pattern > /* </ url-pattern >
?? </ filter-mapping >
?
?????? 而對應(yīng)的 Filter 應(yīng)該都以對應(yīng)的名稱定義為 Spring ApplicationContext 中的一個 bean 。
?? < bean name = "casAuthenticationFilter"
????? class = "org.jasig.cas.client.authentication.AuthenticationFilter"
????? p:casServerLoginUrl = "https://elim:8443/cas/login" p:renew = "false"
????? p:gateway = "false" p:serverName = "http://elim:8080" />
?
?? < bean name = "casTicketValidationFilter"
????? class = "org.jasig.cas.client.validation.Cas10TicketValidationFilter"
????? p:serverName = "http://elim:8080" p:redirectAfterValidation = "true" >
????? < property name = "ticketValidator" >
???????? < bean class = "org.jasig.cas.client.validation.Cas10TicketValidator" >
??????????? <!-- 對應(yīng)于 casServerUrlPrefix -->
??????????? < constructor-arg index = "0" value = "https://elim:8443/cas" />
???????? </ bean >
????? </ property >
?? </ bean >
?
?? < bean id = "casHttpServletRequestWrapperFilter" class = "org.jasig.cas.client.util.HttpServletRequestWrapperFilter" />
??
?? < bean id = "casAssertionThreadLocalFilter" class = "org.jasig.cas.client.util.AssertionThreadLocalFilter" />
?
1.3 ???? 添加證書到信任庫
?????? 在 ticket 驗證成功后,還需要驗證證書,這需要我們將之前建立的證書導(dǎo)出并添加到當(dāng)前 JRE 的證書信任庫中,否則將驗證失敗。 JRE 在尋找證書時將根據(jù)當(dāng)前使用的 host 來尋找,且會用該 host 匹配之前創(chuàng)建證書時指定的用戶名稱,如果匹配則表示找到。這也就意味著我們在 創(chuàng)建證書時指定的用戶名稱需要是我們的 host 。我的機器名稱為“ elim ”,我就把它作為我的 host ,那么對應(yīng)的證書應(yīng)該這樣創(chuàng)建。
keytool -genkey -keyalg RSA -alias tomcat -dname "cn=elim" -storepass changeit
?
?????? 該語句是對我們之前介紹的 keytool -genkey -alias tomcat -keyalg RSA 的精寫,它已經(jīng)通過相應(yīng)的參數(shù)指定了對應(yīng)的參數(shù)值,而不需要再與用戶交互了。如果還用之前的語句生成證書的話,那么對應(yīng)的值應(yīng)該這樣填:
?
?????? 之后會在用戶的對應(yīng)目錄下生成一個 .keystore 文件。之后需要將該文件導(dǎo)出為一個證書到 %JAVA_HOME%/jre/lib/security 目錄下,對應(yīng)指令為:
keytool -export -alias tomcat -file %JAVA_HOME%/jre/lib/security/tomcat.crt -storepass changeit
?
?????? 之后需要將導(dǎo)出的 tomcat.crt 證書添加到運行時使用的 JRE 的受信任證書庫中,此時如果出現(xiàn)異常可將原本 %JAVA_HOME%/jre/lib/security 目錄下的 cacerts 刪除后繼續(xù)執(zhí)行以下指令。
keytool -import -alias tomcat -file %JAVA_HOME%/jre/lib/security/tomcat.crt -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
?
?????? 經(jīng)過以上幾步后就可以啟用我們自己的 Cas Client 應(yīng)用了,然后初次訪問該應(yīng)用時就會跳轉(zhuǎn)到 Cas Server 進(jìn)行登錄認(rèn)證。認(rèn)證成功后將跳轉(zhuǎn)到我們自己的 Client 應(yīng)用進(jìn)行 ticket 的驗證,驗證通過后就可以自由的訪問我們的 Client 應(yīng)用了。
?
(注:本文是基于 Cas Server3.5.2 和 Cas Client3.1.11 所寫)
(注:原創(chuàng)文章,轉(zhuǎn)載請注明出處。原文地址: http://haohaoxuexi.iteye.com/blog/2142631 )
?
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
