内部邀請碼:C8E245J (不寫邀請碼,沒有現金送)
國内私募機構九鼎控股打造,九鼎投資是在全國股份轉讓系統挂牌的公衆公司,股票代碼為430719,為“中國PE第一股”,市值超1000億元。
------------------------------------------------------------------------------------------------------------------------------------------------------------------
單點登入系統CAS搭建及取得更多使用者資訊的實作
一、 單點登入簡介
單點登入(Single sign-on,簡稱為 SSO),是目前比較流行的企業業務整合的解決方案之一。其簡單定義是在多個應用系統中,使用者隻需要登入一次就可以通路所有互相信任的應用系統。類似的,使用者隻需要執行一次退出操作就可以終止對所有相關應用系統的通路。
本文主旨在介紹如何使用CAS實作單點登入時取得更多的使用者資訊,單點登入的原理将不作闡述。
二、 CAS簡介
CAS是CentralAuthentication Service即中央認證服務的簡稱,它是由耶魯大學發起的開源項目,其目的是為Web系統提供可靠的單點登入機制。2004年12月CAS正式成為JASIG(jasig.org)的一個項目。
CAS的Server需要作為獨立Web應用部署,并且目前Server僅支援Java,但其Client端則支援Java、.Net、PHP、Perl、Apache、uPortal、Ruby等多種開發語言。
各版本的CAS可以分别從以下位址下載下傳。
CAS Server的下載下傳位址:http://downloads.jasig.org/cas/
CAS Client的下載下傳位址:http://downloads.jasig.org/cas-clients/
本文驗證環境使用CASServer 3.5.2、CAS Client 3.2.1(Java Client端)、CAS1.3.2(PHP Client端)。目前網上可查找到的大部分資料并不适用于CAS Server 3.5.2,是以如果使用其它版本本文可能也存在不适用的情況。
三、 CAS部署、基本配置與使用
1. Server端部署
将下載下傳的CASServer壓縮包解壓,可以發現這個包中已經包含了Server側的全部源代碼及編譯完成的Jar及war包。将modules目錄下的cas-server-webapp-3.5.2.war拷貝至Tomcat的webapps下後啟動Tomcat即可完成部署。
上述war包中包含了Server的版本号,是以部署完成後的應用通路URL中也會包含版本資訊。如果期望為Client端提供更多的伸縮性和相容性,可以在啟動Tomcat前将war包的名稱修改為cas.war。這樣如果以後Server端進行更新,Client端也不需要做任何修改。
部署完成并啟動Tomcat以後,就可以在浏覽器中輸入對應的URL(例如:http://192.168.202.176:8080/cas)通路CAS了。CAS Server提供的預設界面如下圖所示。
在Username與Password輸入框中輸入相同的字元即可成功登入,登入成功後的界面如下圖。
2. Server端配置
上述部署完成的Server存在兩個問題,一是通過HTTP協定通路服務安全性較差,通常應當使用HTTPS協定以提供較高的安全性;另一方面登入賬号與密碼其實與系統不存在關聯,不能在實際應用中加以使用,是以需要進一步做一些配置解決上述問題。
2.1 配置Tomcat的HTTPS協定
為Tomcat配置HTTPS協定分為兩步,一是為Tomcat生成服務證書并将對應CA根證書導入keystore的可信清單中,二是修改Tomcat的配置檔案。
關于證書生成,不同的應用場景與需求其具體過程會有一些差異。如果對此沒有特殊要求,可以采用最為簡單的自簽名證書。其具體生成過程簡述如下:
建立一個用于儲存證書的目錄,然後通過cmd指令進入指令提示符視窗,并将目前工作目錄切換至剛剛建立的用于儲存證書的目錄(Linux系統過程類似)。随後執行以下指令,其中紅色部分可以根據實際需要加以替換:
keytool-genkey -aliascas_key -keyalg RSA -storepass yanzhijun -keystorecas.keystore-validity 3600
注意:如果上述指令執行時提示找不到keytool,請檢查環境變量JAVA_HOME和PATH是否已正确配置。
随後,keytool會提示輸入一些與證書相關的資訊,例如可以依次輸入下圖中所示值。其中特别要注意的是要輸入的第一項“passport.yanzhijun.net”必須要輸入CAS Server的實際域名或IP,否則在後面建立HTTPS連接配接時将不能建立可信連接配接。其它項可以根據實際情況輸入或随意輸入合法值即可。
上述指令成功執行以後,将生成名為cas.keystore的檔案,Tomcat将使用它來建立HTTPS連接配接。
通常CAS Client端與Server端進行通信時也使用HTTPS協定并通過證書對Server進行身份确認,是以還需要将上述步驟中生成的證書導出備用。以下指令将證書導出為名為cas.cer的檔案:
keytool-export -trustcacerts -alias cas_key -file cas.cer -keystore cas.keystore-storepass yanzhijun
注意:上述指令中的參數要與生成證書的指令中的參數保持對應一緻。
完成證書生成以後,還需要對Tomcat的配置檔案conf/server.xml進行修改。首先将标簽Connectorport="8443"前後的注釋删除,然後将其修改為類似以下内容:
<Connectorport="8443"protocol="org.apache.coyote.http11.Http11NioProtocol"SSLEnabled="true"
maxThreads="150" scheme="https"secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="C:/cert/cas.keystore"
keystorePass="yanzhijun"/>
注意:keystoreFile與keystorePass要與生成證書時輸入的參數保持一緻。
至此,Tomcat的HTTPS協定配置完成,重新啟動Tomcat,就可以使用HTTPS協定通路cas了(例如https://passport.yanzhijun.net:8443/cas)。如果必要,可以将前述生成的cas.cer導入系統或浏覽器的證書庫中,以便通路CAS Server時浏覽器不再顯示警告資訊。
2.2 配置CAS從資料庫中取得資料驗證登入的有效性
CASServer提供了多種身份驗證方法,可以配置檔案WEB-INF/deployerConfigContext.xml中對其加以修改。
在deployerConfigContext.xml中查找“<property name="authenticationHandlers">”,其中以下配置表明登入時Username與Password一緻則通過驗證。
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/>
要使用資料庫中的資訊對登入進行驗證,需要将上述bean配置項注釋或删除掉,然後在對應位置添加以下配置:
<beanclass="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="sql" value="selectpassword fromuserinfowhereuserName=?" />
<property name="dataSource" ref="dataSource"/>
</bean>
其中userinfo是儲存使用者資訊的表名,userName是表userinfo中存儲使用者名的字段名稱,password則是表userinfo中存儲使用者密碼(或加密後的密碼)的字段名稱。
如果資料庫中的密碼是加密存儲的(通常都應加密存儲),則上述配置中需要增加名為“passwordEncoder”的property指定加密方法。例如以下配置指定了以SHA加密密碼。
<propertyname="passwordEncoder">
<beanclass="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-argvalue="SHA"/>
</bean>
</property>
在指定從資料庫中查詢使用者資訊進行驗證時,同時指定了要使用資料源dataSource,是以還需要對資料源進行配置。以下資料源配置使用位于本機的MySQL資料庫,可以将其配置在節點beans下的任意位置。
<beanid="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"value="com.mysql.jdbc.Driver"></property>
<property name="url"value="jdbc:mysql://localhost:3306/cas_usr"></property>
<property name="username"value="cas_usr"></property>
<property name="password"value="123"></property>
注意:使用資料庫時需要将與之對應的資料庫Java驅動拷入WEB-INF/lib目錄下。
至此,CAS Server全部搭建完成。
3. Java Client端配置與使用
首先需要将下載下傳的JavaClient壓縮包解壓并在解壓後将modules目錄下的cas-client-core-3.2.1.jar及commons-logging-1.1.jar拷入準備內建CAS的Web應用的WEB-INF/lib下。
随後需要在Web應用的web.xml檔案中添加CAS的相關配置,具體配置項如下,請注意修改其中CAS Server的位址、端口資訊及目前應用的位址、端口資訊。
<filter>
<filter-name>CASAuthentication Filter</filter-name>
<filter-class>
org.jasig.cas.client.authentication.AuthenticationFilter
</filter-class>
<!-- CAS驗證伺服器位址-->
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>
https://passport.yanzhijun.net:8443/cas/login
</param-value>
</init-param>
<param-name>renew</param-name>
<param-value>false</param-value>
<param-name>gateway</param-name>
<!--用戶端應用伺服器位址-->
<param-name>serverName</param-name>
<param-value>http://192.168.202.176:8080</param-value>
</filter>
<!--負責Ticket校驗-->
<filter>
<filter-name>CASValidation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
<!-- CAS驗證伺服器位址(上下文) -->
<param-name>casServerUrlPrefix</param-name>
<param-value>https://passport.yanzhijun.net:8443/cas/</param-value>
<param-name>useSession</param-name>
<param-value>true</param-value>
<param-name>redirectAfterValidation</param-name>
<filter-name>CASHttpServletRequest Wrapper Filter</filter-name>
org.jasig.cas.client.util.HttpServletRequestWrapperFilter
<filter-name>CASAssertion Thread Local Filter</filter-name>
org.jasig.cas.client.util.AssertionThreadLocalFilter
<filter-mapping>
<filter-name>CAS AuthenticationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-name>CAS HttpServletRequestWrapper Filter</filter-name>
至此,CAS機制已經可以發揮作用了,如果嘗試打開Web應用,可以發現頁面跳轉到了CAS Server的登入頁面,輸入正确的Username與Password并送出後頁面又跳轉回目前Web應用。不過通常Web應用程式也需要知道目前登入的使用者名稱,以下示例JSP頁面顯示了在Web應用中擷取目前登入使用者名的幾種方法。
<%@pageimport="org.jasig.cas.client.authentication.AttributePrincipal" %>
<%@page import="org.jasig.cas.client.validation.Assertion"%>
<%
String loginName1 = request.getRemoteUser();
%>
request.getRemoteUser(): <%=loginName1%><br/>
AttributePrincipal principal = (AttributePrincipal)request.getUserPrincipal();
String loginName2 = principal.getName();
request.getUserPrincipal().getName():<%=loginName2%><br/>
Object object =request.getSession().getAttribute("_const_cas_assertion_");
Assertion assertion =(Assertion)object;
String loginName3 =assertion.getPrincipal().getName();
request.getSession().getAttribute("_const_cas_assertion_").getPrincipal().getName():<%=loginName3%><br/>
檢視上述代碼運作後輸出的結果,可以看到三種方式取得的使用者稱是一樣的,均與登入時的名稱保持一緻。
4. PHP Client端使用
解壓PHP Client壓縮包,将檔案夾CAS及檔案CAS.php拷至需要內建CAS的PHP應用的适當位置。修改docs/examples目錄下的檔案example_simple.php如下,并令example_simple.php與檔案夾CAS及檔案CAS.php同一目錄,在浏覽器中通路example_simple.php,頁面将顯示目前登入的使用者名稱。
<?php
require_once'CAS.php';
phpCAS::client(CAS_VERSION_2_0,'passport.yanzhijun.net',8443, '/cas');
phpCAS::setNoCasServerValidation();
phpCAS::forceAuthentication();
if(isset($_REQUEST['logout'])) {
phpCAS::logout();
}
// forthis test, simply print that the authentication was successfull
?>
<html>
<head>
<title>phpCAS simpleclient</title>
</head>
<body>
<h1>Successfull Authentication!</h1>
<p>the user's login is<b><?php echo phpCAS::getUser(); ?></b>.</p>
<p>phpCAS version is<b><?php echo phpCAS::getVersion(); ?></b>.</p>
<p><ahref="?logout=">Logout</a></p>
</body>
</html>
四、 CAS單點登入取得更多使用者資訊
通過上述部署與配置,多個Web應用已經可以共用一個登入服務。但是,上述過程中作為CAS Client端的Web應用隻取得了使用者登入名稱資訊,而在實際應用中,Web應用往往需要獲得登入使用者更多的資訊,例如會員等級、性别、住址等。要達到此目的,隻需對Server端稍做修改即可實作。
1. 服務端配置及修改
假定上述存儲使用者資訊的資料表userinfo中還包含一個名為address的用于存儲使用者位址的字段,而Web應用程式希望能夠從CAS Server處獲得目前登入使用者的位址資訊,則Server端需要按以下内容修改deployerConfigContext.xml。部配置設定置說明請參見注釋。
<!--将原有attributeRepository配置注釋 -->
<!--
<beanid="attributeRepository"
class="org.jasig.services.persondir.support.StubPersonAttributeDao">
<propertyname="backingMap">
<map>
<entrykey="uid" value="uid" />
<entrykey="eduPersonAffiliation" value="eduPersonAffiliation"/>
<entrykey="groupMembership" value="groupMembership" />
</map>
</property>
-->
<!--新增attributeRepository配置(開始) -->
<bean class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao"id="attributeRepository">
<!-- 指定使用的資料源,此處dataSource是已配置好的資料源 -->
<constructor-arg index="0"ref="dataSource"/>
<!-- 從資料庫中查詢資訊的SQL語句,通常隻需要修改表名即可 -->
<constructor-arg index="1" value="select * fromuserinfo where {0}"/>
<propertyname="queryAttributeMapping">
<map>
<!-- 上述查詢的參數,将userName替換為表中表示使用者名的字段名稱 -->
<entrykey="username" value="userName"/>
</map>
</property>
<propertyname="resultAttributeMapping">
<!-- 需要傳回給Web應用的其它資訊,多個資訊時可繼續增加entry節點-->
<!--key值為資料表中的字段名稱,value值為Client端取值時的名稱辨別-->
<entry key="address" value="address"/>
<!--新增attributeRepository配置(結束) -->
<bean
id="serviceRegistryDao"
class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl">
<propertyname="registeredServices">
<list>
<beanclass="org.jasig.cas.services.RegexRegisteredService">
<propertyname="id" value="0" />
<propertyname="name" value="HTTP and IMAP" />
<propertyname="description" value="Allows HTTP(S) and IMAP(S)protocols" />
<propertyname="serviceId" value="^(https?|imaps?)://.*" />
<propertyname="evaluationOrder" value="10000001" />
<!--增加此項配置 -->
<property name="ignoreAttributes" value="true"/>
</bean>
… …
</list>
</property>
</bean>
CASServer要将額外的資訊傳遞至Client端,還需要修改完成資訊組裝的檔案WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp。casServiceValidationSuccess.jsp負責組裝包含使用者資訊的XML,是以修改部分是将需要傳遞的額外資訊加入到它最終生成的XML檔案之中。具體修改如下:
<cas:serviceResponsexmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess> <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<!-- 新增額外資訊(開始) -->
<c:iftest="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)> 0}">
<cas:attributes>
<c:forEachvar="attr"items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}">
<!--注意此行的正确寫法,網上資料基本都是錯誤的--> <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>
</c:forEach>
</cas:attributes>
</c:if>
<!-- 新增額外資訊(結束) -->
<c:if test="${not emptypgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
<c:if test="${fn:length(assertion.chainedAuthentications)> 1}">
<cas:proxies>
<c:forEachvar="proxy" items="${assertion.chainedAuthentications}"varStatus="loopStatus" begin="0"end="${fn:length(assertion.chainedAuthentications)-2}"step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</cas:proxies>
</cas:authenticationSuccess>
</cas:serviceResponse>
2. Java Client端取得更多使用者資訊
Java Client端不需要做任何修改就可以繼續正常使用CAS服務,如果需要取得使用者更多資訊,可以通過AttributePrincipal對象取得Attribute清單(一個Map對象)後進行查詢。
修改前述Java Client的示例代碼,在最後追加取得address資訊的代碼,重新開機服務并重新通路頁面,可以看到頁面上顯示了目前使用者的address資訊。
<%@pageimport="org.jasig.cas.client.validation.Assertion" %>
<%@page import="java.util.*" %>
<br/>
// 以下代碼取得address資訊
Map<String, Object> attributes = principal.getAttributes();
Iterator it = attributes.keySet().iterator();
String address = "NoAddress";
Object oaddress=attributes.get("address");
if(oaddress != null)
{
address =oaddress.toString();
address:<%=address%><br/>
3. PHP Client端取得更多使用者資訊
PHP Client要取得額外的使用者資訊隻需要直接調用phpCAS::getAttribute即可,例如要取得上述配置中增加的address資訊,隻需要在适當位置增加以下代碼:
phpCAS::getAttribute('address');