Spring Security 3和CAS 3.5.2集成的完整實例

回顧

在上一篇文章,利用Spring Security 3.2的remember-me搭建SSO,我作了一個簡單的單點登陸。但其畢竟不夠健壯,好比沒法點單登出,也沒法與非Spring Security的應用集成。今天,我將使用CAS從新搭建SSO。 html

本實驗架構圖

如下是本實驗要搭建的服務的架構圖,兩個web應用網站和一個認證中心。三個網站均隱藏在Apache reverse proxy身後。java

本實驗使用的軟件:web

  1. Spring Security 3.2spring

  2. CAS 3.5.2apache

  3. Apache Httpd 2.2瀏覽器

  4. Tomcat 8tomcat

  5. OpenLdap 2.4服務器

  6. OpenSSLcookie

CAS原理

這不是本文的重點。但若是不提一下,後面的實驗則可能會比較困難。我在網上找了張別人畫的圖,清晰的解釋了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

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開啓Https

儘管個人CAS server是隱藏在Apache代理的後面,CAS Server仍是要求使用Https進行登陸驗證。在文章Apache httpd開啓SSL中, 我曾經使用openSSL開啓了apache的HTTPS。因爲CAS server隱藏在Apache以後,因此我對CAS server的SSL證書並不在意其權威性,只須要使用本地keytool產生一個證書便可。步驟以下:

  1. 生成服務器證書 keytool -genkey -alias casServer -keyalg RSA -keystore d:/servers/keys/casServerStore, 密碼爲:wwwtestcom。

    特別注意:名字與姓氏必定要使用網站的域名,例如cas.test.com。Cas Client使用httpclient訪問cas server的時候,會嚴格的檢查證書。


  2. 導出證書: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證書。

  3. 修改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 了。

Apache代理CAS Server

爲了節省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客戶端導入SSL證書

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相同的證書。

開發CAS客戶端

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。步驟以下:

  1. 在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>
  2. 由於登錄頁面已經交給了CAS,因此刪除本身的login.jsp。但建立一個403.jsp,用於展現access dennied。

  3. 對Spring Security進行配置。因爲以前我一直用的javaconfig。隨着複雜度的增長,javaconfig愈來愈麻煩,網上也沒教程。XML配置的教程到不少。但實在是不想修改了,因而繼續堅持使用javaconfig。如下是Spring的javaconfig:

    爲了控制字數,我用截圖來展現代碼和註釋。 源碼可下載。


    以前的formLogin()和rememberMe()所有刪除了,由於這兩塊所有交給了CAS Server來作。

  4. 修改index.jsp中的logout URL 

    <a href="${pageContext.request.contextPath }/j_spring_cas_security_logout">logout</a>
  5. 爲了可以單點登出,除了在第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>
  6. 修改完畢,打包部署。

CAS單點登陸的時序圖

爲了更好的理解上面的代碼,我將用時序圖來解釋,當未認證的用戶訪問受保護頁面的時候,代碼都作了哪些工做。

CAS單點登出原理

CAS的單點登出是自動實現了。FirstWeb與SecondWeb使用同一個CAS登陸。用戶在同一個瀏覽器中作了如下幾個操做:

  1. 登陸FirstWeb, 轉向cas server登陸頁面。登陸之後,瀏覽器中存放有cas server的cookie.

  2. 登陸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, 那須要把不信任的證書先導入到服務器運行環境中。

最後的源代碼以及WAR下載

http://pan.baidu.com/s/1Byc9o 其中cas.war是CAS server。其它的均可以根據名字猜出是什麼。

有疑問能夠跟我聯繫,joey.zhangpeng@gmail.com, QQ14687489

改造架構

爲了統一域名,減小SSL證書,讓全部的網站看起來像一個網站,請把上面的架構改形成以下:

  1. 使用同一個域名www.test.com.

  2. 對外使用https,對內所有使用ajp協議。

  3. Apache與cas server之間使用ajp聯繫。

以上是我在本地作的最後改動,步驟和代碼就不上傳了,留給你們本身去嘗試吧。有問題可聯繫。

相關文章
相關標籤/搜索