CAS單點登陸系統

CAS 介紹

CAS 是 Yale 大學發起的一個開源項目,旨在爲 Web 應用系統提供一種可靠的單點登陸方法,CAS 在 2004 年 12 月正式成爲 JA-SIG 的一個項目。CAS 具備如下特色:javascript

  • 開源的企業級單點登陸解決方案。
  • CAS Server 爲須要獨立部署的 Web 應用。
  • CAS Client 支持很是多的客戶端(這裏指單點登陸系統中的各個 Web 應用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

CAS 原理和協議

從結構上看,CAS 包含兩個部分: CAS Server 和 CAS Client。CAS Server 須要獨立部署,主要負責對用戶的認證工做;CAS Client 負責處理對客戶端受保護資源的訪問請求,須要登陸時,重定向到 CAS Server。圖1 是 CAS 最基本的協議過程:php

圖 1. CAS 基礎協議css

CAS 基礎協議

CAS Client 與受保護的客戶端應用部署在一塊兒,以 Filter 方式保護受保護的資源。對於訪問受保護資源的每一個 Web 請求,CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket,若是沒有,則說明當前用戶還沒有登陸,因而將請求重定向到指定好的 CAS Server 登陸地址,並傳遞 Service (也就是要訪問的目的資源地址),以便登陸成功事後轉回該地址。用戶在第 3 步中輸入認證信息,若是登陸成功,CAS Server 隨機產生一個至關長度、惟1、不可僞造的 Service Ticket,並緩存以待未來驗證,以後系統自動重定向到 Service 所在地址,併爲客戶端瀏覽器設置一個 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新產生的 Ticket 事後,在第 5,6 步中與 CAS Server 進行身份合適,以確保 Service Ticket 的合法性。html

在該協議中,全部與 CAS 的交互均採用 SSL 協議,確保,ST 和 TGC 的安全性。協議工做過程當中會有 2 次重定向的過程,可是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對於用戶是透明的。java

另外,CAS 協議中還提供了 Proxy (代理)模式,以適應更加高級、複雜的應用場景,具體介紹能夠參考 CAS 官方網站上的相關文檔。git

 

服務器版本選用 https://github.com/apereo/cas/tree/v4.0.0 github

客戶端選用    https://github.com/Jasig/phpCASweb

 

部署 CAS Server

CAS Server 是一套基於 Java 實現的服務,該服務以一個 Java Web Application 單獨部署在與 servlet2.3 兼容的 Web 服務器上,另外,因爲 Client 與 CAS Server 之間的交互採用 Https 協議,所以部署 CAS Server 的服務器還須要支持 SSL 協議。當 SSL 配置成功事後,像普通 Web 應用同樣將 CAS Server 部署在服務器上就能正常運行了,不過,在真正使用以前,還須要擴展驗證用戶的接口。spring

在 Tomcat 上部署一個完整的 CAS Server 主要按照如下幾個步驟:sql

配置 Tomcat 使用 Https 協議

若是但願 Tomcat 支持 Https,主要的工做是配置 SSL 協議,其配置過程和配置方法能夠參考 Tomcat 的相關文檔。不過在生成證書的過程當中,會有須要用到主機名的地方,CAS 建議不要使用 IP 地址,而要使用機器名或域名。

部署 CAS Server

CAS Server 是一個 Web 應用包,將前面下載的 cas-server-3.1.1-release.zip 解開,把其中的 cas-server-webapp-3.1.1.war 拷貝到 tomcat的 webapps 目錄,並改名爲 cas.war。因爲前面已配置好 tomcat 的 https 協議,能夠從新啓動 tomcat,而後訪問:https://localhost:8443/cas ,若是能出現正常的 CAS 登陸頁面,則說明 CAS Server 已經部署成功。

雖然 CAS Server 已經部署成功,但這只是一個缺省的實現,在實際使用的時候,還須要根據實際概況作擴展和定製,最主要的是擴展認證 (Authentication) 接口和 CAS Server 的界面。

擴展認證接口

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

擴展 AuthenticationHandler

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

清單 1. AuthenticationHandler定義

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 表示驗證失敗。

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

清單 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() 成爲了子類必需要實現的方法。

因爲實際運用中,最經常使用的是用戶名和密碼方式的認證,CAS3 提供了針對該方式的實現,如清單 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-3.1.1-release.zip 包解開後,在 modules 目錄下能夠找到包 cas-server-support-jdbc-3.1.1.jar,其提供了經過 JDBC 鏈接數據庫進行驗證的缺省實現,基於該包的支持,咱們只須要作一些配置工做便可實現 JDBC 認證。

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

1. 配置 DataStore

打開文件 %CATALINA_HOME%/webapps/cas/WEB-INF/deployerConfigContext.xml,添加一個新的 bean 標籤,對於 DB2,內容如清單 4 所示:

清單 4. 配置 DataStore

<bean id="casDataSource" class="org.apache.commons.dbcp.BasicDataSource">
     <property name="driverClassName">
          <value>com.ibm.db2.jcc.DB2Driver</value>
     </property>
     <property name="url">
          <value>jdbc:db2://9.125.65.134:50000/CASTest</value>
     </property>
     <property name="username">
          <value>db2user</value>
     </property>
     <property name="password">
          <value>db2password</value>
     </property>
</bean>

其中 id 屬性爲該 DataStore 的標識,在後面配置 AuthenticationHandler 會被引用,另外,須要提供 DataStore 所必需的數據庫驅動程序、鏈接地址、數據庫登陸用戶名以及登陸密碼。

2. 配置 AuthenticationHandler

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

使用哪一個 AuthenticationHandler,須要在 deployerConfigContext.xml 中設置,默認狀況下,CAS 使用一個簡單的 username=password 的 AuthenticationHandler,在文件中能夠找到以下一行:<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePassword
AuthenticationHandler" />,咱們能夠將其註釋掉,換成咱們但願的一個 AuthenticationHandler,好比,使用QueryDatabaseAuthenticationHandler 或 SearchModeSearchDatabaseAuthenticationHandler 能夠分別選取清單 5 或清單 6 的配置。

清單 5. 使用 QueryDatabaseAuthenticationHandler

<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(?)" />
</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所示:

清單 7. 添加 passwordEncoder

<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 中設置的實際加密器類的引用:

清單 8. 指定具體加密器類

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

這裏 MyPasswordEncoder 是根據實際狀況本身定義的加密器,實現 PasswordEncoder 接口及其 encode() 方法。

3. 部署依賴包

在以上配置完成之後,須要拷貝幾個依賴的包到 cas 應用下,包括:

  • 將 cas-server-support-jdbc-3.1.1.jar 拷貝到 %CATALINA_HOME%/webapps/cas/ WEB-INF/lib 目錄。
  • 數據庫驅動,因爲這裏使用 DB2,將 %DB2_HOME%/java 目錄下的 db2java.zip (改名爲 db2java.jar), db2jcc.jar, db2jcc_license_cu.jar 拷貝到 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目錄。對於其餘數據庫,一樣將相應數據庫驅動程序拷貝到該目錄。
  • DataStore 依賴於 commons-collections-3.2.jar, commons-dbcp-1.2.1.jar, commons-pool-1.3.jar,須要到 apache 網站的 Commons 項目下載以上 3 個包放進 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目錄。

擴展 CAS Server 界面

CAS 提供了 2 套默認的頁面,分別爲「 default 」和「 simple 」,分別在目錄「 cas/WEB-INF/view/jsp/default 」和「 cas/WEB-INF/view/jsp/simple 」下。其中 default 是一個稍微複雜一些的頁面,使用 CSS,而 simple 則是能讓 CAS 正常工做的最簡化的頁面。

在部署 CAS 以前,咱們可能須要定製一套新的 CAS Server 頁面,添加一些個性化的內容。最簡單的方法就是拷貝一份 default 或 simple 文件到「 cas/WEB-INF/view/jsp 」目錄下,好比命名爲 newUI,接下來是實現和修改必要的頁面,有 4 個頁面是必須的:

  • casConfirmView.jsp: 當用戶選擇了「 warn 」時會看到的確認界面
  • casGenericSuccess.jsp: 在用戶成功經過認證而沒有目的Service時會看到的界面
  • casLoginView.jsp: 當須要用戶提供認證信息時會出現的界面
  • casLogoutView.jsp: 當用戶結束 CAS 單點登陸系統會話時出現的界面

CAS 的頁面採用 Spring 框架編寫,對於不熟悉 Spring 的使用者,在修改以前須要熟悉該框架。

頁面定製完事後,還須要作一些配置從而讓 CAS 找到新的頁面,拷貝「 cas/WEB-INF/classes/default_views.properties 」,重命名爲「 cas/WEB-INF/classes/ newUI_views.properties 」,並修改其中全部的值到相應新頁面。最後是更新「 cas/WEB-INF/cas-servlet.xml 」文件中的 viewResolver,將其修改成如清單 9 中的內容。

清單 9. 指定 CAS 頁面

<bean id="viewResolver" 
     class="org.springframework.web.servlet.view.ResourceBundleViewResolver" p:order="0">
    <property name="basenames">
        <list>
            <value>${cas.viewResolver.basename}</value>
            <value> newUI_views</value>
        </list>
    </property>
</bean>

部署客戶端應用

PHP客戶端下載地址:http://downloads.jasig.org/cas-clients/php/,目前最新版本爲CAS-1.2.0.ORC2

新建項目:phpCasClient.將CAS文件夾和CAS.php複製到工程中,修改CAS/client.php,將其中的https改成http,新建php文件:user.php,此文件用於處理單點登錄,內容以下:

 

</pre><p><pre name="code" class="php"><pre name="code" class="php">
<?php
class user
{
    /**
     * Logs out the current user and redirect to homepage.
     */
    public function logout()
    {
        session_start();
        //啓用單點登陸的時候還要到統一認證中心註銷  && isset($_SESSION['phpCAS']) && $_SESSION['phpCAS']['auth_checked'] == '1'   

        //引人cas  
        include_once '/CAS-1.2.0/CAS.php';

        // initialize phpCAS              
        //phpCAS::client(CAS_VERSION_2_0,'服務地址',端口號,'cas的訪問地址');  
        phpCAS::client(CAS_VERSION_2_0,"192.168.142.1","80","/cas");

        //方法一:登出成功後跳轉的地址 -- 登出方法中加此句  
        /* 
        phpCAS::setServerLoginUrl("https://192.168.142.1:80/cas/logout?embed=true&service=http://localhost/phpCasClient/user.php?a=login"); 
        //no SSL validation for the CAS server 
        phpCAS::setNoCasServerValidation(); 
        phpCAS::logout();*/

        //方法二:退出登陸後返回地址 -- 登出方法中加此句  
        phpCAS::setNoCasServerValidation();$param=array("service"=>"http://localhost/phpCasClient/user.php?a=login");
        phpCAS::logout($param);
    }

    /**
     * @desc LoginCas()單點登錄
     */
    public function loginCas(){
        Header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
        //引人cas  
        include 'CAS-1.2.0/CAS.php';
        // initialize phpCAS   
        //phpCAS::client(CAS_VERSION_2_0,'服務地址',端口號,'cas的訪問地址');  
        phpCAS::client(CAS_VERSION_2_0,"192.168.142.1","80","/cas",true);

        //能夠不用,用於調試,能夠經過服務端的cas.log看到驗證過程。  
        // phpCAS::setDebug();  
        //登錄成功後跳轉的地址 -- 登錄方法中加此句  
        phpCAS::setServerLoginUrl("https://192.168.142.1:80/cas/login?embed=true&cssUrl=http://localhost/phpCasClient/style/login.css&service=http://localhost/phpCasClient/user.php?a=loginCas");
        //no SSL validation for the CAS server 不使用SSL服務校驗  
        phpCAS::setNoCasServerValidation();
        //這裏會檢測服務器端的退出的通知,就能實現php和其餘語言平臺間同步登出了  
        phpCAS::handleLogoutRequests();

        if(phpCAS::checkAuthentication()){
            //獲取登錄的用戶名  
            $username=phpCAS::getUser();
            //用戶登錄成功後,採用js進行頁面跳轉  
            echo "<script language=\"javascript\">parent.location.href='http://localhost/phpCasClient/home.php';</script>";
        }else{
            // 訪問CAS的驗證  
            phpCAS::forceAuthentication();
        }
        exit;
    }
}
?>

新建視圖層,login.html,該頁面即爲單點登錄頁面,內容以下:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>單點登錄界面</title>
</head>
<body>
<div class="view">
    <table style="width:600; height:333; border:1; align:center; cellpadding:4; bordercolor:#CCCCCC;">
        <tr>
            <td style="width: 50%;align:center;">
                請輸入登錄信息:
            </td>
            <td style="width: 50%;">
                <div id="maincol" style="border: 1px solid #ccc;float: left;width: 400px;height: 219px; overflow: hidden;">
                    <iframe id="auth-login-iframe" name="auth-login-iframe" style="width:100%; height:100%; marginwidth:0; marginheight:0; frameborder:0; scrolling:no;" src="http://localhost/phpCasClient/user.php?a=loginCas"></iframe>
                </div>
            </td>
        </tr>
        <tr>
            <span style="white-space:pre"> </span><td><a href="http://localhost/phpCasClient/user.php?a=logout">登出</a></td>
        </tr>
    </table>
</div>
</body>
</html>

注意:php配置文件php.ini須要開啓php_curl,即 找到 ;extension=php_curl.dll ,將該句前面的分號去掉便可,改成 extension=php_curl.dll

安裝解析xml的擴展

什麼是DOM?


文檔對象模型(Document Object Model,DOM)是一種用於HTML和XML文檔的編程接口。它給文檔提供了一種結構化的表示方法,能夠改變文檔的內容和呈現方式。咱們最爲關心的是,DOM把網頁和腳本以及其餘的編程語言聯繫了起來。


什麼是PHP DOM Extension ?


DOM Extension容許php使用DOM處理xml文檔。相關手冊說明:
http://www.php.net/manual/en/intro.dom.php


如何安裝PHP DOM Extension?

 


在CentOS系統下是很好安裝的,運行

#yum install php-xml
重啓一下apache
#service httpd restart

 


如何檢查系統已經安裝好了PHP DOM Extension ?

 


使用phpinfo()函數測試
#vi phpinfo.php
<?
   phpinfo();
?>
http://localhost/phpinfo.php會看到PHP測試頁面。中間有如下幾行說明已安裝好。

DOM/XML

enabled

DOM/XML API Version

20031129

libxml Version

2.6.26

HTML Support

enabled

XPath Support

enabled

XPointer Support

enabled

Schema Support

enabled

RelaxNG Support

enabled

 

 

此時,訪問login.html,即可以看到單點登錄的界面,登錄成功後,頁面跳轉到home.php中.

點擊 登出,控制層請求user.php的logout方法,處理登出請求.登出成功後,頁面跳轉到login.html,提示用戶登錄

 

至此,php使用CAS的單點登錄,登出已經操做完畢.

相關文章
相關標籤/搜索