在上一篇文章,利用Spring Security 3.2的remember-me搭建SSO,我作了一個簡單的單點登陸。但其畢竟不夠健壯,好比沒法點單登出,也沒法與非Spring Security的應用集成。今天,我將使用CAS從新搭建SSO。 html
如下是本實驗要搭建的服務的架構圖,兩個web應用網站和一個認證中心。三個網站均隱藏在Apache reverse proxy身後。java
本實驗使用的軟件:web
Spring Security 3.2spring
CAS 3.5.2apache
Apache Httpd 2.2瀏覽器
Tomcat 8tomcat
OpenLdap 2.4服務器
OpenSSLcookie
這不是本文的重點。但若是不提一下,後面的實驗則可能會比較困難。我在網上找了張別人畫的圖,清晰的解釋了CAS是如何工做的。session
簡單的講,CAS client將用戶轉向CAS server,用戶在CAS server上登錄,產生了ticket。CAS server將用戶轉回CAS client頁面,帶着ticket參數。 CAS client悄悄的使用httpClient向CAS server索取用戶所有信息,憑證就是ticket。CAS server將用戶信息返回給CAS client,client對用戶進行檢驗並重定向到原始請求的頁面。
CAS Server只是一個部署在服務器上的java web應用,而且其使用的是Spring工具包,因此在配置CAS和LDAP上,跟Spring的ldap配置幾乎相同。我仍是使用以前配好的OpenLDAP,見OpenLDAP搭建。
CAS server的源碼和安裝文件都在一塊兒,能夠從http://www.jasig.org/cas/download下載。下載完之後,將裏面的maven項目所有導入到eclipse中。建議使用Spring tool suite。Eclipse在導入這些maven項目時,會有一些錯誤。怕麻煩的話,就用STS吧。
在項目cas-server-webapp下,編輯pom.xml,增長ldap的依賴:
<dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-support-ldap</artifactId> <version>${project.version}</version> </dependency>
而後打開WEB-INF下的deployerConfigContext.xml,在最後面添加:
<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource"> <property name="pooled" value="false"/> <property name="url" value="ldap://127.0.0.1:389/dc=mycompany,dc=com" /> <property name="userDn" value="cn=admin,dc=mycompany,dc=com"/> <property name="password" value="admin"/> <property name="baseEnvironmentProperties"> <map> <entry key="com.sun.jndi.ldap.connect.timeout" value="3000" /> <entry key="com.sun.jndi.ldap.read.timeout" value="3000" /> <entry key="java.naming.security.authentication" value="simple" /> </map> </property> </bean>
註釋掉SimpleTestUsernamePasswordAuthenticationHandler並加入新的代碼:
<!-- <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> --> <bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler" p:filter="uid=%u" p:searchBase="ou=people" p:contextSource-ref="contextSource" />
另外須要註冊cas client,不然cas server則不信任client發送的任何請求。在deployerConfigContext.xml找到serviceRegistryDao。本文使用最簡單的方式:
<bean class="org.jasig.cas.services.RegexRegisteredService"> <property name="id" value="0" /> <property name="name" value="HTTPS and IMAPS" /> <property name="description" value="Allows HTTPS and IMAPS protocols" /> <property name="serviceId" value="^(https?|imaps?)://.*" /> <property name="evaluationOrder" value="10000001" /> </bean>
maven install將cas-server-webapp構建成cas.war,而後部署在tomcat4上。啓動tomcat4,能夠訪問http://localhost:8003/cas/login並嘗試用ldap用戶登陸。
儘管個人CAS server是隱藏在Apache代理的後面,CAS Server仍是要求使用Https進行登陸驗證。在文章Apache httpd開啓SSL中, 我曾經使用openSSL開啓了apache的HTTPS。因爲CAS server隱藏在Apache以後,因此我對CAS server的SSL證書並不在意其權威性,只須要使用本地keytool產生一個證書便可。步驟以下:
生成服務器證書 keytool -genkey -alias casServer -keyalg RSA -keystore d:/servers/keys/casServerStore, 密碼爲:wwwtestcom。
特別注意:名字與姓氏必定要使用網站的域名,例如cas.test.com。Cas Client使用httpclient訪問cas server的時候,會嚴格的檢查證書。
導出證書:keytool -export -file d:/servers/keys/casServer.crt -alias casServer -keystore d:/servers/keys/casServerStore, 導出的證書須要導入cas client jvm中。但本例咱們將cas server隱藏在了apache proxy的後面了,因此並不須要導入此證書。而咱們須要向cas client中導入的是apache proxy的SSL證書。
修改TOMCAT配置文件:tomcat/conf/server.xml
首先關閉APR模式。 在APR模式下,SSL的配置參數跟非APR模式徹底不同,詳細請參考tomcat手冊。
<!--APR library loader. Documentation at /docs/apr.html <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> -->
而後開啓tomcat的https connector.
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="d:/servers/keys/casServerStore" keystorePass="wwwtestcom"/>
重啓tomcat4,你能夠訪問https://cas.test.com:8443/cas/login 了。
爲了節省IP,繼續使用以前firstWeb與secondWeb共用的apache。共用Apache的方法有一個缺陷,我後面會提到。
首先爲咱們新的auth.test.com產生新的SSL證書,請根據以前文章中列出的步驟作:OpenSSL生成證書步驟。記住Common Name必須爲auth.test.com. 咱們將獲得authServer.crt與authServer.key.
而後在httpd-ssl.conf中添加新的逆向代理,爲了節省文字,不突破上限1萬字,我用圖片代替代碼:
重啓apache,如今可使用https://www.test.com/cas/login來訪問cas服務器了。
CAS client是跑在tomcat上的一個servletFilter。CAS client使用HttpClient與CAS server打交道,由於中間存在着apache代理,因此HttpClient實際是與apache打交道。若是httpClient的環境不信任apache的SSL證書,就會產生SSLHandshakeException和SunCertPathBuilderException:
因此須要把apache的證書導入到tomcat的jvm中。因爲openssl產生的authServer.crt並非x509證書,沒法被導入到java cacerts中。須要將authServer.crt轉爲x509格式.
>openssl x509 -in authServer.crt -inform PEM -out authServer.der -outform DER
而後將authServer.der導入java的cacerts中:
keytool -import -keystore %JAVA_HOME%/jre/lib/security/cacerts -file pathto/authServer.der -alias auth.test.com
注意,這裏有個bug。我導入了證書之後,CASClient仍是報SunCertPathBuilderException,我向JVM加啓動參數
-Djavax.net.debug=all
來調試SSL握手過程。發現接受的證書並非auth.test.com的,而是來自於httpd-ssl.conf中<VirtualHost _default_:443>定義中的證書。我猜測httpclient在進行SSL握手的時候,使用的是IP地址,因此轉向了_default_虛擬網站,並獲取了它的證書。爲了解決此問題,我不得不把<VirtualHost _default_:443>中的證書指定與auth.test.com相同的證書。
FirstWeb與SecondWeb若想經過CAS服務器來作登陸驗證,須要與CAS的客戶端集成。CAS客戶端在java web上則是一個servlet filter。接下來配置firstWeb與secondWeb的CAS客戶端。
如下代碼修改都是基於前一篇文章開啓Remember Me SSO以前的Firstweb與SecondWeb.這兩個的初始源碼能夠從下面地址下載:
修改前的FirstWeb的源碼在此:http://pan.baidu.com/s/1nthpuDN
修改前的secondWeb的源碼能夠從這裏下載http://pan.baidu.com/s/1o64TQ9k
我只展現如何修改secondWeb。步驟以下:
在pom.xml中聲明對CAS client的依賴:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> <version>3.2.0.RELEASE</version> <scope>compile</scope> </dependency>
由於登錄頁面已經交給了CAS,因此刪除本身的login.jsp。但建立一個403.jsp,用於展現access dennied。
對Spring Security進行配置。因爲以前我一直用的javaconfig。隨着複雜度的增長,javaconfig愈來愈麻煩,網上也沒教程。XML配置的教程到不少。但實在是不想修改了,因而繼續堅持使用javaconfig。如下是Spring的javaconfig:
爲了控制字數,我用截圖來展現代碼和註釋。 源碼可下載。
以前的formLogin()和rememberMe()所有刪除了,由於這兩塊所有交給了CAS Server來作。
修改index.jsp中的logout URL
<a href="${pageContext.request.contextPath }/j_spring_cas_security_logout">logout</a>
爲了可以單點登出,除了在第3步中註冊的SingleSignOutFilter,(注意SingleSignOutFilter須要註冊在CsrfFilter以前,不然會被Csrf阻擋)。還須要在web.xml中添加下面一段代碼。此外,還須要把https://second.test.com的SSL證書導入到CAS服務器的JVM中。原理在下面講。
<listener> <listener-class> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listener-class> </listener>
修改完畢,打包部署。
爲了更好的理解上面的代碼,我將用時序圖來解釋,當未認證的用戶訪問受保護頁面的時候,代碼都作了哪些工做。
CAS的單點登出是自動實現了。FirstWeb與SecondWeb使用同一個CAS登陸。用戶在同一個瀏覽器中作了如下幾個操做:
登陸FirstWeb, 轉向cas server登陸頁面。登陸之後,瀏覽器中存放有cas server的cookie.
登陸SecondWeb,直接使用瀏覽器中的cas server cookie,自動登陸了。
用戶在完成上面的操做時,cas server端自動的未來自firstWeb和secondWeb網站的service URL和它們共用的cas server cookie存儲起來了。
FirstWeb的service url是https://first.test.com/firestWeb/html/j_spring_cas_security_check
SecondWeb的service url是https://second.test.com/secondWeb/j_spring_cas_security_check
當用戶註銷firstWeb的時候,會轉向cas的登出頁面,cas服務器會根據firstWeb提交的cookie,而列出全部跟此cookie綁定在一塊兒的service url. 而後cas服務器會經過httpclient向全部的service url發送下面的請求:
post SERVICE_URL?LogoutRequest=<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-29-s7cF2OLhk7I3fN03ifDMTMbQ4tPedaR0jTJ" Version="2.0" IssueInstant="2014-03-04T01:38:14Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID><samlp:SessionIndex>ST-28-ZvyU0hUzbMkzLiQqnkdY-cas.test.com</samlp:SessionIndex></samlp:LogoutRequest>
上面的請求會被SingleSignOutFilter所截獲。而後銷燬其session。
注意:既然是使用了httpclient發送請求,若是service url使用的是https, 那須要把不信任的證書先導入到服務器運行環境中。
http://pan.baidu.com/s/1Byc9o 其中cas.war是CAS server。其它的均可以根據名字猜出是什麼。
有疑問能夠跟我聯繫,joey.zhangpeng@gmail.com, QQ14687489
爲了統一域名,減小SSL證書,讓全部的網站看起來像一個網站,請把上面的架構改形成以下:
使用同一個域名www.test.com.
對外使用https,對內所有使用ajp協議。
Apache與cas server之間使用ajp聯繫。
以上是我在本地作的最後改動,步驟和代碼就不上傳了,留給你們本身去嘗試吧。有問題可聯繫。