CAS 單點登錄

1、Tomcat配置SSL

1. 生成 server key

以命令方式換到目錄%TOMCAT_HOME%,在command命令行輸入以下命令: 
keytool -genkey -alias tomcat_key -keyalg RSA -storepass changeit -keystore server.keystore -validity 3600 
用戶名輸入域名,如localhost(開發或測試用)或 hostname.domainname(用戶擁有的域名),其它所有以Enter跳過,最後確認,此時會在%TOMCAT_HOME%下生成server.keystore文件。html

2. 將證書導入的JDK的證書信任庫中

這步對於Tomcat的SSL配置不是必須,但對於CAS SSO是必須的,不然會出現以下錯誤: 
edu.yale.its.tp.cas.client.CASAuthenticationException: Unable to validate ProxyTicketValidate. 
導入過程分2步,第一步是導出證書,第二步是導入到證書信任庫,命令以下: 
keytool -export -trustcacerts -alias tomcat_key -file server.cer -keystore server.keystore -storepass changeit 
keytool -import -trustcacerts -alias tomcat_key -file server.cer -keystore D:/」Program Files」/Java/jdk1.8.0_60/jre/lib/security/cacerts -storepass changeit 
若是有提示,輸入Y就能夠了。 
其它有用keytool命令(列出信任證書庫中全部已有證書,刪除庫中某個證書): 
keytool -list -keystore %JAVA_HOME%/jre/lib/security/cacerts >t.txt 
keytool -delete -trustcacerts -alias tomcat_key -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit 
注意:CAS 建議不要使用 IP 地址,而要使用機器名或域名。java

3.配置Tomcat

在Tomcat的server.xml配置文件中加入: mysql

2、部署CAS Server到Tomcat

  1. 到cas官網下載cas-server http://developer.jasig.org/cas/(我下載的是4.0.0)
  2. 解壓壓縮文件,在解壓後的文件夾內找到/modules/cas-server-webapp-4.0.0.war。將其複製到%Tomcat_Home%\webapps下並更名爲cas.war
  3. 啓動Tomcat,並測試 https://localhost:8443/cas 看是否訪問正常(默認輸入用戶名和密碼一致就能夠)。 
    注:CAS Server 4.0.0 默認登錄驗證方式是 AcceptUsersAuthenticationHandler (老版本好像是SimpleTestUsernamePasswordAuthenticationHandler),默認用戶名/密碼爲 casuser/Mellon(cas/WEB-INF/deployerConfigContext.xml 中找到 id=primaryAuthenticationHandler 的bean查看,裏面的map也能夠本身增長更多個)。咱們一般須要從數據庫中取出用戶名和密碼進行驗證,因此咱們須要修改 deployerConfigContext.xml,配置咱們本身的服務認證方式。

3、配置服務認證

CAS Server 負責完成對用戶的認證工做,它會處理登陸時的用戶憑證 (Credentials) 信息,用戶名/密碼對是最多見的憑證信息。CAS Server 可能須要到數據庫檢索一條用戶賬號信息,也可能在 XML 文件中檢索用戶名/密碼,還可能經過 LDAP Server 獲取等,在這種狀況下,CAS 提供了一種靈活但統一的接口和實現分離的方式,實際使用中 CAS 採用哪一種方式認證是與 CAS 的基本協議分離開的,用戶能夠根據認證的接口去定製和擴展。git

擴展 AuthenticationHandler

CAS 提供擴展認證的核心是 AuthenticationHandler 接口,該接口定義如清單 1 下:github

清單 1. AuthenticationHandler定義web

public interface AuthenticationHandler {
    /**
     * Method to determine if the credentials supplied are valid. * @param credentials The credentials to validate. * @return true if valid, return false otherwise. * @throws AuthenticationException An AuthenticationException can contain * details about why a particular authentication request failed. */ boolean authenticate(Credentials credentials) throws AuthenticationException; /** * Method to check if the handler knows how to handle the credentials * provided. It may be a simple check of the Credentials class or something * more complicated such as scanning the information contained in the * Credentials object. * @param credentials The credentials to check. * @return true if the handler supports the Credentials, false othewrise. */ boolean supports(Credentials credentials); }

 

該接口定義了 2 個須要實現的方法,supports ()方法用於檢查所給的包含認證信息的Credentials 是否受當前 AuthenticationHandler 支持;而 authenticate() 方法則擔當驗證認證信息的任務,這也是須要擴展的主要方法,根據狀況與存儲合法認證信息的介質進行交互,返回 boolean 類型的值,true 表示驗證經過,false 表示驗證失敗。spring

CAS中還提供了對 AuthenticationHandler 接口的一些抽象實現,好比,可能須要在執行authenticate() 方法先後執行某些其餘操做,那麼可讓本身的認證類擴展自清單 2 中的抽象類:sql

清單 2. AbstractPreAndPostProcessingAuthenticationHandler定義數據庫

public abstract class AbstractPreAndPostProcessingAuthenticationHandler implements AuthenticateHandler{ protected Log log = LogFactory.getLog(this.getClass()); protected boolean preAuthenticate(final Credentials credentials) { return true; } protected boolean postAuthenticate(final Credentials credentials, final boolean authenticated) { return authenticated; } public final boolean authenticate(final Credentials credentials) throws AuthenticationException { if (!preAuthenticate(credentials)) { return false; } final boolean authenticated = doAuthentication(credentials); return postAuthenticate(credentials, authenticated); } protected abstract boolean doAuthentication(final Credentials credentials) throws AuthenticationException; }

AbstractPreAndPostProcessingAuthenticationHandler 類新定義了 preAuthenticate() 方法和 postAuthenticate() 方法,而實際的認證工做交由 doAuthentication() 方法來執行。所以,若是須要在認證先後執行一些額外的操做,能夠分別擴展 preAuthenticate()和 ppstAuthenticate() 方法,而 doAuthentication() 取代 authenticate() 成爲了子類必需要實現的方法。apache

因爲實際運用中,最經常使用的是用戶名和密碼方式的認證,CAS 提供了針對該方式的實現,如清單 3 所示:

清單 3. AbstractUsernamePasswordAuthenticationHandler 定義

public abstract class AbstractUsernamePasswordAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler{ ... protected final boolean doAuthentication(final Credentials credentials) throws AuthenticationException { return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials); } protected abstract boolean authenticateUsernamePasswordInternal( final UsernamePasswordCredentials credentials) throws AuthenticationException; protected final PasswordEncoder getPasswordEncoder() { return this.passwordEncoder; } public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } ... }

基於用戶名密碼的認證方式可直接擴展自 AbstractUsernamePasswordAuthenticationHandler,驗證用戶名密碼的具體操做經過實現 authenticateUsernamePasswordInternal() 方法達到,另外,一般狀況下密碼會是加密過的,setPasswordEncoder() 方法就是用於指定適當的加密器。 
從以上清單中能夠看到,doAuthentication() 方法的參數是 Credentials 類型,這是包含用戶認證信息的一個接口,對於用戶名密碼類型的認證信息,能夠直接使用 UsernamePasswordCredentials,若是須要擴展其餘類型的認證信息,須要實現Credentials接口,而且實現相應的 CredentialsToPrincipalResolver 接口,其具體方法能夠借鑑 UsernamePasswordCredentials 和 UsernamePasswordCredentialsToPrincipalResolver。

JDBC 認證方法 
用戶的認證信息一般保存在數據庫中,所以本文就選用這種狀況來介紹。將前面下載的 cas-server-{version}-release.zip 包解開後,在 modules 目錄下能夠找到包 cas-server-support-jdbc-{version}.jar,其提供了經過 JDBC 鏈接數據庫進行驗證的缺省實現,基於該包的支持,咱們只須要作一些配置工做便可實現 JDBC 認證。

JDBC 認證方法支持多種數據庫,DB2, OracleMySQL, Microsoft SQL Server 等都可,這裏以 DB2 做爲例子介紹。而且假設DB2數據庫名: CASTest,數據庫登陸用戶名: db2user,數據庫登陸密碼: db2password,用戶信息表爲: userTable,該表包含用戶名和密碼的兩個數據項分別爲 userName 和 password。

1. 配置 DataSource

打開文件 cas/WEB-INF/deployerConfigContext.xml,添加一個新的 bean 標籤,以mysql爲例,內容如清單 4 所示:

<!-- Data source definition --> <bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/test</value> </property> <property name="username"> <value>sites</value> </property> <property name="password"> <value>123456</value> </property> </bean>

其中 casDataSource 在後面配置 AuthenticationHandler 會被引用,添加數據庫驅動程序、鏈接地址、數據庫登陸用戶名以及登陸密碼。

2. 配置 AuthenticationHandler

CAS 4.0.0 提供了 3 個基於 JDBC 的 AuthenticationHandler,分別爲 BindModeSearchDatabaseAuthenticationHandler, QueryDatabaseAuthenticationHandler, SearchModeSearchDatabaseAuthenticationHandler。 
- BindModeSearchDatabaseAuthenticationHandler 是用所給的用戶名和密碼去創建數據庫鏈接,根據鏈接創建是否成功來判斷驗證成功與否; 
- QueryDatabaseAuthenticationHandler 經過配置一個 SQL 語句查出密碼,與所給密碼匹配; 
- SearchModeSearchDatabaseAuthenticationHandler 經過配置存放用戶驗證信息的表、用戶名字段和密碼字段,構造查詢語句來驗證。

使用哪一個 AuthenticationHandler,須要在 deployerConfigContext.xml 中設置,默認狀況下,CAS 4.0.0 使用 AcceptUsersAuthenticationHandler(上面已經提到,須要在 deployerConfigContext.xml 中查看)。

在 deployerConfigContext.xml 文件中能夠找到以下配置:

<bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="casuser" value="Mellon"/> </map> </property> </bean>

咱們能夠將其註釋掉,換成咱們但願的一個 AuthenticationHandler,好比,使用QueryDatabaseAuthenticationHandler 或 SearchModeSearchDatabaseAuthenticationHandler 能夠分別選取清單 5 或清單 6 的配置。 
清單 5. 使用 QueryDatabaseAuthenticationHandler

<bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="dataSource" ref="casDataSource " /> <property name="sql" value="select password from userTable where lower(userName) = lower(?)" /> <!-- 指定密碼加密器(可選) --> <property name="passwordEncoder" ref="passwordEncoder" /> </bean> <!-- 密碼加密器(能夠指定本身實現的加密器) --> <bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder" > <constructor-arg name="encodingAlgorithm" value="MD5"/> <property name="characterEncoding" value="UTF-8"/> </bean>

清單 6. 使用 SearchModeSearchDatabaseAuthenticationHandler

<bean id="SearchModeSearchDatabaseAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler" abstract="false" singleton="true" lazy-init="default" autowire="default" dependency-check="default"> <property name="tableUsers"> <value>userTable</value> </property> <property name="fieldUser"> <value>userName</value> </property> <property name="fieldPassword"> <value>password</value> </property> <property name="dataSource" ref="casDataSource " /> </bean>

另外,因爲存放在數據庫中的密碼一般是加密過的,因此 AuthenticationHandler 在匹配時須要知道使用的加密方法,在 deployerConfigContext.xml 文件中咱們能夠爲具體的 AuthenticationHandler 類配置一個 property,指定加密器類,好比對於 QueryDatabaseAuthenticationHandler,能夠修改如清單7所示:

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="dataSource" ref=" casDataSource " /> <property name="sql" value="select password from userTable where lower(userName) = lower(?)" /> <property name="passwordEncoder" ref="myPasswordEncoder"/> </bean>

其中 myPasswordEncoder 是對清單 8 中設置的實際加密器類的引用:

<bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>

這裏 MyPasswordEncoder 是根據實際狀況本身定義的加密器,實現 PasswordEncoder 接口及其 encode() 方法。 
注意:DataSource 依賴於 commons-collections-3.2.jar、commons-dbcp-1.2.1.jar、commons-pool-1.3.jar、數據庫驅動包、cas對jdbc的支持包cas-server-support-jdbc-4.0.0.jar,須要找到這幾個jar包放進 %TOMCAT_HOME%/webapps/cas/WEB-INF/lib 目錄。

而後即可以啓動Tomcat使用數據庫中的用戶名和密碼進行登陸測試了。

4、擴展 CAS Server 界面

最直接的方法,下載官方源碼,直接修改 ^_^ 
我想你們頗有必要具有「任何開源項目均可以本身動手,使用經過源碼構建項目並運行」的能力,萬一你須要在源碼上加點東西呢。 
CAS 4.0 默認的頁面存放在 cas/WEB-INF/view/jsp/default 下面,若是是源碼項目頁面存放在 cas-server-webapp\src\main\webapp\WEB-INF\view\jsp\default\ui 目錄中。 
咱們來看一下 cas\WEB-INF\classes\default_views.properties 文件,若是是源碼項目,文件位置爲:cas-server-webapp\src\main\resources\default_views.properties

### Login view (/login) casLoginView.(class)=org.springframework.web.servlet.view.JstlView casLoginView.url=/WEB-INF/view/jsp/default/ui/casLoginView.jsp ### Display login (warning) messages casLoginMessageView.(class)=org.springframework.web.servlet.view.JstlView casLoginMessageView.url=/WEB-INF/view/jsp/default/ui/casLoginMessageView.jsp ### Login confirmation view (logged in, warn=true) casLoginConfirmView.(class)=org.springframework.web.servlet.view.JstlView casLoginConfirmView.url=/WEB-INF/view/jsp/default/ui/casConfirmView.jsp ### Logged-in view (logged in, no service provided) casLoginGenericSuccessView.(class)=org.springframework.web.servlet.view.JstlView casLoginGenericSuccessView.url=/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp ### Logout view (/logout) casLogoutView.(class)=org.springframework.web.servlet.view.JstlView casLogoutView.url=/WEB-INF/view/jsp/default/ui/casLogoutView.jsp ### CAS error view viewServiceErrorView.(class)=org.springframework.web.servlet.view.JstlView viewServiceErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp viewServiceSsoErrorView.(class)=org.springframework.web.servlet.view.JstlView viewServiceSsoErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp ### CAS statistics view viewStatisticsView.(class)=org.springframework.web.servlet.view.JstlView viewStatisticsView.url=/WEB-INF/view/jsp/monitoring/viewStatistics.jsp ### Expired Password Error message casExpiredPassView.(class)=org.springframework.web.servlet.view.JstlView casExpiredPassView.url=/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp ### Locked Account Error message casAccountLockedView.(class)=org.springframework.web.servlet.view.JstlView casAccountLockedView.url=/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp ### Disabled Account Error message casAccountDisabledView.(class)=org.springframework.web.servlet.view.JstlView casAccountDisabledView.url=/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp ### Must Change Password Error message casMustChangePassView.(class)=org.springframework.web.servlet.view.JstlView casMustChangePassView.url=/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp ### Bad Hours Error message casBadHoursView.(class)=org.springframework.web.servlet.view.JstlView casBadHoursView.url=/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp ### Bad Workstation Error message casBadWorkstationView.(class)=org.springframework.web.servlet.view.JstlView casBadWorkstationView.url=/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp

咱們根據文件清單對應的文件修改便可。 
若是你想保留這些頁面,拷貝一份default 目錄,而後修改其中對應的文件後,在這個配置文件中將對應路徑修改即可。

除此以外,CAS 4.0 還提供了一些協議登陸的方式,也就是在不經過登陸頁面使用帳號密碼的方式登陸,自動受權登陸。 
好比咱們能夠從其餘平臺直接免登錄進入原來須要SSO登陸後才能夠看到的系統,或者作個U盾,插入U盾的電腦,能夠直接訪問系統,無需登陸。 
官方文檔:http://jasig.github.io/cas/4.0.x/protocol/CAS-Protocol.html#cas-protocol

5、部署客戶端應用

單點登陸的目的是爲了讓多個相關聯的應用使用相同的登陸過程,本文在講解過程當中構造 2個簡單的應用,分別以 casTest1 和 casTest2 來做爲示例,它們均只有一個頁面,顯示歡迎信息和當前登陸用戶名。這 2 個應用使用同一套登陸信息,而且只有登陸過的用戶才能訪問,經過本文的配置,實現單點登陸,即只需登陸一次就能夠訪問這兩個應用。 
1. 與 CAS Server 創建信任關係 
- CAS Server 部署在服務器A 
- 客戶端應用部署在服務B\C 
因爲客戶端應用與 CAS Server 的通訊採用 SSL,所以須要A與B\C的jre之間創建信任關係。 
CAS Server 咱們在文章的前面已經配置好的 SSL,如今咱們只須要在客戶端B\C上添加服務器A的證書,操做方法:

網上找一個這個文件下載下來 InstallCert.java 
而後在客戶端服務器上 
編譯:javac InstallCert.java 
運行:Java InstallCert serverA:8443 其中8443爲SSL端口,serverA爲服務器名稱或域名 
而且在接下來出現的詢問中輸入 1。這樣,就將 A 添加到了 B 的 trust store 中(其餘客戶端服務器同樣操做)。

這裏要說一下的是 serverA:8443 這裏不要寫 {IP地址}:8443,要以域名的方式,如本機 localhost:8443 ,或域名方式 www.baidu.com:443

2. 配置 CAS Filter 
在Client工程WEB-INF/lib下添加 cas-client-core-3.3.3.jar 包。 
修改web.xml以下:

<!-- ======================== 單點登陸/登出 ======================== --> <!-- 該過濾器用於實現單點登出功能,可選配置。 --> <!-- 登出地址 https://casserver:8443/cas/logout --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <!-- 該過濾器負責用戶的認證工做,必須啓用它 --> <filter> <filter-name>CAS Authentication Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://localhost:8443/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8080</param-value> </init-param> </filter> <!-- 該過濾器負責對Ticket的校驗工做,必須啓用它 --> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class>org.jasig.cas.client.validation.Cas10TicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>https://localhost:8443/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8080</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> </filter> <!-- 該過濾器負責實現HttpServletRequest請求的包裝, 好比容許開發者經過HttpServletRequest的getRemoteUser()方法得到SSO登陸用戶的登陸名,可選配置。 --> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <!-- 該過濾器使得開發者能夠經過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 Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Authentication Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- ======================== 單點登陸/登出結束 ======================== -->

運行Client工程,首次訪問任一頁面就會跳轉到https://localhost:8443/cas/login進行認證。 
把你的退出連接設置爲:https://localhost:8443/cas/logout 便可實現單點登出,若是須要在系統退出後返回指定頁面,咱們能夠修改源碼在logout後面增長參數來實現。

3. 登陸用戶名的獲取

// 1. request.getRemoteUser(); // 2. AssertionHolder.getAssertion().getPrincipal().getName()

6、單點登陸測試截圖

我建立了2個測試項目,首頁訪問路徑分別爲: 
http://localhost:8080/cas-client-test1/index.jsp 
http://localhost:8080/cas-client-test2/index.jsp

一、訪問 http://localhost:8080/cas-client-test1/index.jsp 自動跳轉到認證頁面。

這裏寫圖片描述

二、登陸成功後,自動轉到 http://localhost:8080/cas-client-test1/index.jsp 頁面。

這裏寫圖片描述

三、訪問 http://localhost:8080/cas-client-test2/index.jsp 由於已經被認證,因此能夠直接打開不用再登陸。

這裏寫圖片描述

四、退出後打開退出頁面,實際項目中能夠經過 httpclient 請求登出地址來進行登出,而後跳轉到登陸頁面。

這裏寫圖片描述

相關文章
相關標籤/搜索