JA-SIG(CAS)學習筆記3

技術背景知識: 
JA-SIG CAS服務環境搭建,請參考 :JA-SIG(CAS)學習筆記1 
JA-SIG CAS業務架構介紹,請參考 :JA-SIG(CAS)學習筆記2 
HTTPS所涉及的Java安全證書知識,請參考 :Java keytool 安全證書學習筆記 

CAS技術框架 

CAS Server 
目前,咱們使用的CAS Server 3.1.1的是基於Spring Framework編寫的,所以在CAS服務器端的配置管理中,絕大多數是Spring式的Java Bean XML配置。CAS 的服務器提供了一套易於定製的用戶認證器接口,用戶能夠根據自身企業的在線系統的認證方式,來定製本身的認證邏輯。不管是傳統的用戶名/密碼方式,仍是基於安全證書的方式;是基於關係數據庫的存儲,仍是採用LDAP服務器,CAS Server給咱們提供了這些經常使用的驗證器模板代碼,只要稍做修改,即可靈活使用了。 
對於廣大的中國企業用戶而言,另外一個須要定製的功能莫過於全中文、企業特點的用戶身份認證頁面了。CAS Server提供了兩套系統界面,一套是默認的CAS英文標準頁面,另外一套則是專門提供給用戶來定製修改的。(PS:老外們作事情就是人性化啊~~)那麼對CAS Server端的後續學習,咱們將圍繞着身份認證模塊定製和界面定製這兩方面展開。 

CAS Client 
客戶端咱們使用的是CAS Client 2.1.1。雖然在官方網站上已出現了3.1.0版本的下載,但該版本地代碼已經徹底重寫,使用的package和類名同2.1.1截然不同了,最關鍵的是,該版本暫時沒有對應的API說明文檔。雖然咖啡我對程序版本懷有極大的「喜新厭舊」的心態,但安全起見,仍是先2.1.1吧,相信3.1.0的文檔耶魯大學的大牛們已經在整理了,期待中…… 
CAS Client2.1.1.jar中的代碼是至關精煉的,有興趣的朋友建議閱讀一下源碼。Jar包中的代碼分紅三個大部分 
1. edu.yale.its.tp.cas.util 包,其中只有一個工具類 SecureURL.java 用來訪問HTTPS URL 
2. edu.yale.its.tp.cas.proxy包,用來處理Proxy Authentication代理認證的3個類,其中ProxyTicketReceptor.java是 接收PGT回調的servlet,在下文中咱們會說起。 
3. edu.yale.its.tp.cas.client包,其中包含了CAS Filter ,Tag Library等主要的認證客戶端工具類,咱們在後面會進行重點介紹。 
針對CAS Client的學習,咱們的重點將放在CAS Filter 和ProxyTicketReceptor 的配置以及在Java SE環境下,直接使用 ServiceTicketValidator進行Ticket認證明現上。 

CAS服務器端應用 
定製適合你的身份認證程序 
經過前面的學習,咱們瞭解了CAS具備一個良好而強大的SSO功能框架。接下來,咱們要學習如何將實際企業應用中的身份認證同CAS進行整合。 
簡單的說,要將現有企業應用中的認證集成到CAS Server中,只要實現一個名爲AuthenticationHandler的一個認證處理Java接口就行。如下是該接口的源代碼:
public interface AuthenticationHandler {
/**
* 該方法決定一個受支持的credentials是不是可用的,
* 若是可用,該方法返回true,則說明身份認證經過
*/
boolean  authenticate(Credentials credentials)  throws  AuthenticationException;
/**
* 該方法決定一個credentials是不是當前的handle所支持的
*/
boolean  supports(Credentials credentials);
}

這裏咱們要說明一下Credentials這個CAS的概念。所謂Credentials是由外界提供給CAS來證實自身身份的信息,簡單的如一個用戶名/密碼對就是一個Credentials,或者一個通過某種加密算法生成的密文證書也能夠是一個Credentials。在程序的實現上,Credentials被聲明爲一個可序列化的接口,僅僅起着標識做用,源代碼以下: 
public interface Credentials extends Serializable {
    // marker interface contains no methods
}

CAS的API中,已經爲咱們提供了一個最經常使用的實現UsernamePasswordCredentials 用戶名/密碼憑證,代碼以下: 
public class UsernamePasswordCredentials implements Credentials {
    /** Unique ID for serialization. */
    private static final long serialVersionUID = -8343864967200862794L;
    /** The username. */
    private String username;
    /** The password. */
    private String password;
    public final String getPassword() {
        return this.password;
    }
    public final void setPassword(final String password) {
        this.password = password;
    }
    public final String getUsername() {
        return this.username;
    }
    public final void setUsername(final String userName) {
        this.username = userName;
    }
    public String toString() {
        return this.username;
    }
    public boolean equals(final Object obj) {
        if (obj == null || !obj.getClass().equals(this.getClass())) {
            return false;
        }
        final UsernamePasswordCredentials c = (UsernamePasswordCredentials) obj;
        return this.username.equals(c.getUsername())
            && this.password.equals(c.getPassword());
    }
    public int hashCode() {
        return this.username.hashCode() ^ this.password.hashCode();
    }
}

很簡單不是嗎?就是存儲一個用戶名和密碼的java bean而已。 
接下來,咱們將一個Credentials傳給一個AuthenticationHandler進行認證,首先調用boolean supports(Credentials credentials)方法察看當前傳入的Credentials實例,AuthenticationHandler實例現是否支持它?若是支持,再調用boolean authenticate(Credentials credentials)方法進行認證。因爲用戶名/密碼方式是最經常使用的認證方法,所以CAS爲咱們提供了一個現成的基於該方式的抽象認證處理類AbstractUsernamePasswordAuthenticationHandler。一般咱們只須要繼承該類,並實現其中的 authenticateUsernamePasswordInternal方法便可。下面咱們給出一個Demo的實現類,它的校驗邏輯很簡單——僅校驗用戶名的字符長度是否與密碼的相等(這裏密碼是一個表示長度的整數),若是相等則認爲認證經過,請看代碼: 
public class UsernameLengthAuthnHandler
                       extends AbstractUsernamePasswordAuthenticationHandler {
protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials)  throws AuthenticationException {
/*
* 這裏咱們徹底能夠用本身的認證邏輯代替,好比將用戶名/密碼傳入一個SQL語句
* 向數據庫驗證是否有對應的用戶帳號,這不是咱們最常常乾的事麼?
* 只須要將下面的程序替換掉就OK了!!So  easy,so  simple!
/
    String username = credentials.getUsername();
    String password = credentials.getPassword();
    String correctPassword = Integer.toString(username.length());
    return correctPassword.equals(password);
}
}

介紹到這裏,你們應該清楚如何定製本身的AuthenticationHandler類了吧!這裏要附帶說明的是,在CAS Server的擴展API中已經提供了大量經常使用認證形式的實現類,它們同CAS Server的war包一同分發: 
cas-server-support-generic-3.1.1.jar ——使用Map記錄用戶認證信息的實現 
cas-server-support-jdbc-3.1.1.jar —— 基於Spring JDBC的數據庫實現(咱們經常使用的) 
cas-server-support-ldap-3.1.1.jar —— 基於LDAP的用戶認證明現 
更多其餘形式的實現各位看官有興趣的,能夠一一閱讀源碼。 

配置你的身份認證程序 
完成了定製認證類的代碼編寫,接下來就是要讓CAS Server來調用它了。在CAS的框架中,對程序的配置都是使用Spring Framework的xml文件,這對於熟悉Spring的程序員而言算得心應手了。 
配置文件位於應用部署目錄的WEB-INF子目錄下——deployerConfigContext.xml。在bean id=authenticationManager 的 authenticationHandlers屬性中配置咱們的AuthenticationHandlers: 
引用

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:p="http://www.springframework.org/schema/p" 
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> 

<bean id="authenticationManager" 
class="org.jasig.cas.authentication.AuthenticationManagerImpl"> 
。。。 
。。。 
<property name="authenticationHandlers"> 
<list> 
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" /> 
<!—下面就是系統默認的驗證器配置,你能夠替換它,或者增長一個新的handler --> 
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> 
</list> 
</property> 
</bean> 
。。。 
。。。 
</beans>

咱們發現authenticationHandlers屬性是一個list,在這個list中能夠配置多個AuthenticationHandlers。這些AuthenticationHandlers造成了一個驗證器鏈,全部提交給CAS的Credentials信息將經過這個驗證器鏈的鏈式過濾,只要這鏈中有一個驗證器經過了對Credentials的驗證,就認爲這個Credentials是合法的。這樣的設計使得咱們能夠很輕鬆的整合不一樣驗證體系的已有應用到同一個CAS上,好比:A驗證器負責校驗alpha系統提交的Credentials,它是基於LDAP服務器的;B驗證器負責校驗beta系統提交的Credentials,它是一個傳統的RDB用戶表認證;C驗證器負責校驗gamma系統提交的基於RSA證書加密的Credentials。3種徹底不一樣的用戶身份認證經過配置就能夠統一在同一個CAS服務內,很好很強大,不是嗎!! 

定製身份驗證登陸界面 
CAS Server在顯示界面層view使用了「主題Theme」的概念。在{project.home}/webapp/WEB-INF/view/jsp/目錄下,系統默認提供了兩套得UI —— default和simple 。default方案使用了CSS等相對複雜得界面元素,而simple方案提供了最簡化的界面表示方式。在整個的CAS Server服務器端,有四個界面是咱們必需要實現的: 
casConfirmView.jsp —— 確認信息(警告信息)頁面 
casGenericSuccess.jsp —— 登錄成功提示頁面 
casLoginView.jsp —— 登陸輸入頁面 
casLogoutView.jsp —— SSO登出提示頁面 
這些都是標準的jsp頁面,如何實現他們,徹底由您說了算,除了名字不能改。 

CAS爲view的展現提供了3個級別的定製方式,讓咱們從最直觀簡單的開始吧。 

1. 採用文件覆蓋方式:直接修改default中的頁面或者將新寫好的四個jsp文件覆蓋到default目錄中。這種方式最直觀和簡單,但咖啡建議各位在使用這種方式前將原有目錄中的文件備份一下,以備不時之需。 

2. 修改UI配置文件,定位UI目錄:在CAS Server端/webapp/WEB-INF/classes/ 目錄下,有一個名爲default_views.properties的屬性配置文件,你能夠經過修改配置文件中的各個頁面文件位置,指向你新UI文件,來達到修改頁面展現的目的。 

3. 修改配置文件的配置文件,這話看起來有點彆扭,其實一點不難理解。在方法2中的default_views.properties文件是一整套的UI頁面配置。若是我想保存多套的UI頁面配置就能夠寫多個的properties文件來保存這些配置。在CAS Server端/webapp/WEB-INF/目錄下有cas-servlet.xml和cas.properties兩個文件,cas-servlet.xml使用了cas.properties文件中的cas.viewResolver.basename屬性來定義view屬性文件的名字,所以你能夠選者直接修改cas-servlet.xml中的viewResolver 下的basenames屬性,或者修改cas.properties中的cas.viewResolver.basename屬性,指定新的properties文件名,這樣能夠輕鬆的替換全套UI。 

CAS客戶端配置及API應用 
CASFilter的配置 
對於大部分web應用而言,使用CAS集成統一認證是相對簡單的事,只要爲須要認證的URL配置edu.yale.its.tp.cas.client.filter.CASFilter認證過濾器。下面咱們就針對過濾器的配置進行說明。首先參看一下Filter的基本配置: 
引用
<web-app> 
... 
<filter> 
<filter-name>CAS Filter</filter-name> 
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class> 
<init-param> 
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name> 
<param-value>https://secure.its.yale.edu/cas/login<;/param-value> 
</init-param> 
<init-param> 
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name> 
<param-value>https://secure.its.yale.edu/cas/serviceValidate<;/param-value> 
</init-param> 
<init-param> 
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name> 
<param-value>your server name and port (e.g., www.yale.edu:8080)</param-value> 
</init-param> 
</filter> 

<filter-mapping> 
<filter-name>CAS Filter</filter-name> 
<url-pattern>/requires-cas-authetication/*</url-pattern> 
</filter-mapping> 
... 
</web-app>


上述配置中的init-param是filter的3個必備的屬性,下面這張表則是filter所有屬性的詳細說明:java

ProxyTicketReceptor的配置 
你們還記得在前面咱們說過的Proxy Authentication中的call back URL嗎?ProxyTicketReceptor是部署在client端的一個servlet,提供server端回傳PGT和PGTIOU的。它的xml部署以下:程序員

引用

<web-app> 
... 
<servlet> 
<servlet-name>ProxyTicketReceptor</servlet-name> 
<servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class> 
<init-param> 
<param-name>edu.yale.its.tp.cas.proxyUrl</param-name> 
<param-value>https://secure.its.yale.edu/cas/proxy<;/param-value> 
</init-param> 
</servlet> 

<servlet-mapping> 
<servlet-name>ProxyTicketReceptor</servlet-name> 
<url-pattern>/CasProxyServlet</url-pattern> 
</servlet-mapping> 
... 
</webapp>



這裏要說明的是它的參數edu.yale.its.tp.cas.proxyUrl。在服務端經過ProxyTicketReceptor將PGT和PGTIOU傳給客戶端後,ProxyTicketReceptor在進行Proxy Authentication的過程當中須要向服務端請求一個ProxyTicket(PT),這個proxyUrl就是服務端的請求入口了。(關於Proxy Authentication的運做原理,參見JA-SIG(CAS)學習筆記2 ) 

CAS Client端的API應用1.用戶能夠經過如下兩種方式的任意一種,從JSP或servlet中獲取經過認證的用戶名:web

引用
String username = (String)session.getAttribute(CASFilter.CAS_FILTER_USER); 
或者 
String username = (String)session.getAttribute("edu.yale.its.tp.cas.client.filter.user");



2.得到更完整的受認證用戶信息對象CASReceipt Java Bean,可使用如下語句的任一:算法

引用
CASReceipt receipt = (CASReceipt )session.getAttribute(CASFilter.CAS_FILTER_RECEIPT); 
或者 
CASReceipt receipt = (CASReceipt )session.getAttribute("edu.yale.its.tp.cas.client.filter.receipt");



3.手工編碼使用CAS Java Object進行用戶驗證,使用ServiceTicketValidator或者 ProxyTicketValidator(代理認證模式下),在servlet中對用戶身份進行驗證。 
3-1.ServiceTicketValidatorspring

import edu.yale.its.tp.cas.client.*; 
 ...
 String user = null;
 String errorCode = null;
 String errorMessage = null;
 String xmlResponse = null;
 
 /* instantiate a new ServiceTicketValidator */
 ServiceTicketValidator sv = new ServiceTicketValidator();
 
 /* set its parameters */
 sv.setCasValidateUrl("https://secure.its.yale.edu/cas/serviceValidate");
 sv.setService(urlOfThisService);
 sv.setServiceTicket(request.getParameter("ticket")); 
 
 String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet"; 
 sv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
 
 /* contact CAS and validate */
 sv.validate();
 
 /* if we want to look at the raw response, we can use getResponse() */
 xmlResponse = sv.getResponse();
 
 if(sv.isAuthenticationSuccesful()) {
  user = sv.getUser();
 } else {
  errorCode = sv.getErrorCode();
  errorMessage = sv.getErrorMessage();
 }
  /* The user is now authenticated. */
  /* If we did set the proxy callback url, we can get proxy tickets with: */
  String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
  String proxyTicket = ProxyTicketReceptor.getProxyTicket( sv.getPgtIou() , urlOfTargetService);



3-2.ProxyTicketValidator數據庫

import edu.yale.its.tp.cas.client.*;
 ... 
 String user = null;
 String errorCode = null;
 String errorMessage = null;
 String xmlResponse = null;
 List proxyList = null;
 
 /* instantiate a new ProxyTicketValidator */
 ProxyTicketValidator pv = new ProxyTicketValidator(); 
 
 /* set its parameters */
 pv.setCasValidateUrl("https://secure.its.yale.edu/cas/proxyValidate");
 pv.setService(urlOfThisService);
 pv.setServiceTicket(request.getParameter("ticket")); 
 
 String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
 pv.setProxyCallbackUrl(urlOfProxyCallbackServlet); 
 
 /* contact CAS and validate */
 pv.validate();
 
 /* if we want to look at the raw response, we can use getResponse() */
 xmlResponse = pv.getResponse(); 
 
 /* read the response */
 if(pv.isAuthenticationSuccesful()) {
  user = pv.getUser();
  proxyList = pv.getProxyList();
 } else {
  errorCode = pv.getErrorCode();
  errorMessage = pv.getErrorMessage();
  /* handle the error */
 } 
 /* The user is now authenticated. */ 
 /* If we did set the proxy callback url, we can get proxy tickets with this method call: */ 
 String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed"; 
 String proxyTicket = ProxyTicketReceptor.getProxyTicket( pv.getPgtIou() , urlOfTargetService);



在這裏,咱們假設上下文環境中的用戶已經經過了CAS登陸認證,被重定向到當前的servlet下,咱們在servlet中獲取ticket憑證,servlet的URL對用戶身份進行確認。若是上下文參數中沒法獲取ticket憑證,咱們就認爲用戶還沒有登陸,那麼,該servlet必須負責將用戶重定向到CAS的登陸頁面去。 

初戰告捷 
到今天爲止,咱們已經經過JA-SIG學習筆記的1-3部分,對CAS這個開源SSO的框架有了個大致的瞭解和初步的掌握,但願這些知識能爲各位步入CAS殿堂打開一扇的大門。咖啡但願在從此的工做應用中,能同你們一塊共同探討,進一步深刻了解CAS。 

學無止境。。。。。。安全

相關文章
相關標籤/搜索