單點登錄Cas項目實踐

jopen 10年前發布 | 26K 次閱讀 單點登錄 OpenID/單點登錄SSO


Cas 實現單點登錄的實際應用


根據實際項目開發過程對Cas單點登錄做了梳理,在吸收了前輩們的寶貴經驗的基礎上記錄全套開發過程,如下


一、CAS版本
     Cas Server 3.5.2
     Cas Client   3.2.1


二、Cas Server 部署
     下載完整War包,可以直接部署到tomcat的webapps目錄下,不做任何修改即刻成功,我部署的路徑是 localhost:8080/casweb3.5.2,瀏覽器訪問出現登錄頁面,默認輸入任意同樣的登錄名和密碼即刻登錄成功


1、修改登錄頁面:
         一般來說系統實際使用時都不會直接已這個登錄頁面提供個用戶使用,多少都會以實際情況作修改:
          cas統一認證的登陸頁面位于:cas目錄/WEB-INF/view/jsp/default 文件夾里,其中ui/casLoginView.jsp為登陸頁面
          首先我們復制一份default文件夾 重命名為myview
          然后復制一份 /WEB-INF/classes/default_views.properties  命名為default_views_my.properties 打開default_views_my.properties 修改登陸頁面的路徑為我們復制的myview 文件夾(casLoginView.url=/WEB-INF/view/jsp/myview/ui/casLoginView.jsp)
          修改/WEB-INF/cas.properties 中 cas.viewResolver.basename =default_views_my
         到這一步我們只是將登陸頁面拷貝了一份然后指向這份拷貝,接下來我們就可以隨意修改我們拷貝的頁面,這樣做的目的是如果以后想還原回來比較方便,只需要修改引用就行,  頁面修改可以搜索前輩的樣例;
2、修改驗證方式:
          簡單的相同用戶名密碼的驗證在實際應用中肯定是要不換掉的,下面就修改成基本的數據庫驗證
          修改WEN-INF/deployerConfigContext.xml
          搜索<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />,將其注釋掉
          這段是CAS默認的登錄方式,即用戶名和密碼相同即可通過認證。我們現在需要改造它使之可以通過數據庫查詢的用戶名和密碼進行驗證。

          其他是有這樣一個數據庫(casserver)一張數據表(tsys_user)

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">   
      <property name="sql" value="select password from tsys_user where username=? " />   
      <property name="dataSource" ref="dataSource" />   
</bean>
dataSource配置,位置不要配置錯了,不要直接配置bean id="authenticationManager" 這個里面,不然會加載出錯,調試時就遇到這樣情況
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
             <property name="driverClassName">  
                  <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>  
             </property>  
             <property name="url">  
                  <value>jdbc:sqlserver://localhost:1435; DatabaseName=casserver</value>  
             </property>  
             <property name="username">  
                <value>sa</value>  
           </property>  
           <property name="password">  
                <value>sa</value>  
           </property>            
</bean> 

 

這樣做已基本滿足大部分業務的需求,但如果遇到一些特殊的需求,如要求登錄支持用戶名、郵箱地址 或者需要自定義的加密驗證等
自定義一個驗證Handler,繼承AbstractJdbcUsernamePasswordAuthenticationHandler.java.
    public class AlQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler  {  
    
        //@NotNull  
        private String sql;  
    
        protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {  
    
            final String id = getPrincipalNameTransformer().transform(credentials.getUsername());  
            final String password = credentials.getPassword();  
    
            try {  
    
                final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, id);  
    
                System.out.print("Username:" + id);  
                System.out.print("dbPassword:" + dbPassword);  
    
                return MD5.verifyPassword(MD5.getMD5ofStr(password)+id, dbPassword);  
    
            } catch (final IncorrectResultSizeDataAccessException e) {  
                return false;  
            }  
        }  
    
        /** 
         * @param sql The sql to set. 
         */  
        public void setSql(final String sql) {  
            this.sql = sql;  
        }  
    }  

 

修改WEN-INF/deployerConfigContext.xml 配置
<bean class="com.aolong.cas.authentication.handler.AlQueryDatabaseAuthenticationHandler">  
           <property name="sql" value="select password from tsys_user where loginName=? " />    
           <property name="dataSource" ref="dataSource" />    
</bean> 

二、Cas Client 配置
客戶端的jar,如cas-client-core-3.2.1.jar引入到web應用程序的classpath中
     配置web.xml文件, 主要是添加過濾器攔截通信, 下面的實例代碼, 假設web應用程序的端口是9988
1. web.xml 配置
<!-- 與CAS Single Sign Out Filter配合,注銷登錄信息  -->     
     <listener>    
          <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>    
     </listener>    

     <!-- CAS Server 通知 CAS Client,刪除session,注銷登錄信息  -->    
    <filter>     
        <filter-name>CAS Single Sign Out Filter</filter-name>    
        <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>    
     </filter>     
     <filter-mapping>    
        <filter-name>CAS Single Sign Out Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
     </filter-mapping>    

    <!-- 登錄認證,未登錄用戶導向CAS Server進行認證 -->    
    <filter>     
         <filter-name>CAS Filter</filter-name>    
         <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>  
         <!--<filter-class>com.aolong.eip.portal.sys.servlet.RemoteAuthenticationFilter</filter-class>  
         <init-param>      
            <param-name>localLoginUrl</param-name>    
            <param-value>http://localhost:9988/login.jsp</param-value>    
        </init-param>-->  
        <init-param>    
            <param-name>casServerLoginUrl</param-name>    
            <param-value>http://localhost:8080/casweb3.5.2/login</param-value>    
        </init-param>    
        <init-param>    
            <param-name>serverName</param-name>    
            <param-value>http://localhost:9988</param-value>    
        </init-param>    
     </filter>     
     <filter-mapping>    
        <filter-name>CAS Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
     </filter-mapping>     

     <!-- CAS Client向CAS Server進行ticket驗證 -->    
    <filter>      
        <filter-name>CAS Validation Filter</filter-name>    
        <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>    
        <init-param>    
            <param-name>casServerUrlPrefix</param-name>    
            <param-value>http://localhost:8080/casweb3.5.2</param-value>    
        </init-param>    
        <init-param>    
            <param-name>serverName</param-name>    
            <param-value>http://localhost:9988</param-value>    
         </init-param>    
    </filter>    
    <filter-mapping>    
         <filter-name>CAS Validation Filter</filter-name>    
         <url-pattern>/*</url-pattern>    
    </filter-mapping>    

    <!-- 封裝request, 支持getUserPrincipal等方法-->    
    <filter>     
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>    
        <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>    
    </filter>    
    <filter-mapping>    
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
    </filter-mapping>    

    <!-- 存放Assertion到ThreadLocal中   -->    
    <filter>     
        <filter-name>CAS Assertion Thread Local Filter</filter-name>    
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>    
     </filter>     
    <filter-mapping>    
        <filter-name>CAS Assertion Thread Local Filter</filter-name>    
        <url-pattern>/*</url-pattern>    
     </filter-mapping>    

    <!-- 自定義過濾器,單點登錄成功后,寫入自己的Session   -->    
     <filter>    
        <display-name>casSetUserAdapterFilter</display-name>    
        <filter-name>casSetUserAdapterFilter</filter-name>    
        <filter-class>com.aolong.eip.portal.sys.servlet.CasSetUserAdapterFilter</filter-class>    
    </filter>    
    <filter-mapping>    
        <filter-name>casSetUserAdapterFilter</filter-name>    
        <url-pattern>/*</url-pattern>    
    </filter-mapping>
注意這些配置應該盡量放在其他過濾器前面,比如Struts的配置, 如果順序搞錯,它的過濾器想不會起作用,這一點是容易被忽略的

自定義的過濾器代碼:

 
public class CasSetUserAdapterFilter implements Filter {  

    @Override  
    public void init(FilterConfig paramFilterConfig) throws ServletException {  

    }  

    @Override  
    public void doFilter(ServletRequest paramServletRequest,  
            ServletResponse paramServletResponse, FilterChain paramFilterChain)  
            throws IOException, ServletException {  

        HttpServletRequest httpRequest = (HttpServletRequest) paramServletRequest;    
        HttpServletResponse httpResponse = (HttpServletResponse) paramServletResponse;    
        HttpSession session = httpRequest.getSession();  

        //_const_cas_assertion_是CAS中存放登錄用戶名的session標志    
        Object object = session.getAttribute("_const_cas_assertion_");    
        Object userId  = session.getAttribute(SessionUtils.SKEY_USERID);  

        if (object != null && ((userId == null) || ("".equals(userId.toString())))) {    

            Assertion assertion = (Assertion) object;    
            String loginName = assertion.getPrincipal().getName();    

            ILoginService loginService = (ILoginService) SpringContextUtil.getBean("loginService");  

            loginService.ssoLogin(loginName, httpRequest, httpResponse);  

            if (loginPage >= 0)  
            {  
                session.setAttribute(SessionUtils.SKEY_LOGINPAGE, this.loginPage);  
            }  
        }    
        paramFilterChain.doFilter(paramServletRequest, paramServletResponse);    
    }  

    protected void setLoginPage(int iLoginPage)  
    {  
        this.loginPage = iLoginPage;  
    }  

    @Override  
    public void destroy() {  

    }  

    private int loginPage = -1;  

}  
三、安全配置

通過以上的配置基本實現Cas的功能, 但是至此會發現,登錄成功后再訪問其客戶端時又要需要再重新登錄
兩個CAS Client 應用本身是沒有問題的,都可以單獨認證。問題還是出現在CAS Server上,因為即使不附帶service參數進行Login,在CAS Server登錄后,再次輸入CAS Server Login的地址也是出現輸入密碼界面,而不是應該的直接顯示已登錄的界面 ,應該是登錄成功后的TGC根本沒有被加到瀏覽器上,問題在于:由于客戶沒有SSL的證書,暴露的是CAS Server的HTTP Login頁面。而CAS Server默認會給TGC的Cookie加上secure選項,就是只有在SSL下CAS Server下一次才能獲得這個Cookie,所以用戶登錄后再次訪問CAS Server的HTTP頁面,服務器獲取不到TGC,認為這個用戶還沒有登錄,就跳轉到登錄界面了
1、解決的辦法當然是啟用HTTPS的Login頁面 - 當然,我們的客戶沒有證書,自簽名的證書在瀏覽器默認設置還會彈出警告,所以不能用這個辦法;或者修改CAS Server的配置
WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml
文件中修改p:cookieSecure屬性為false就好了。雖然這個會降低SSO的安全性

2、對于Cas取消Https協議的所有配置如下(其中其他的配置修改我尚未理解其真正的作用):
修改 deployerConfigContext.xml 配置文件,添加 p:requireSecure="false"  屬性。
 
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"  
      p:httpClient-ref="httpClient" p:requireSecure="false"/>  
修改 ticketGrantingTicketCookieGenerator.xml 配置文件,p:cookieSecure="true"  改為 p:cookieSecure="false"
 
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"  
  p:cookieSecure="true"  
  p:cookieMaxAge="-1"  
  p:cookieName="CASTGC"  
  p:cookiePath="/cas" />
修改 warnCookieGenerator.xml 配置文件,p:cookieSecure="true"  改為 p:cookieSecure="false"
 
<bean id="warnCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"  
  p:cookieSecure="true"  
  p:cookieMaxAge="-1"  
  p:cookieName="CASPRIVACY"  
  p:cookiePath="/cas" />
四、 單點登出

CAS3.4版本已經很好的支持了單點注銷功能

假設環境如下:
 
兩個業務系統APP1和APP2
在沒有配置單點退出時,效果是這樣子的
1:登錄APP1,然后經過CAS認證后進入APP1
再訪問APP2無需要認證
2:在APP1中連接到cas的logout地址,現象注銷成功界面,然后再訪問APP1,還是可以進去的,因為APP1將用戶的登錄票據存入了session。
 
那么實現了單點退出后的效果應該是這樣子的:
1:登錄APP1,然后經過CAS認證后進入APP1
再訪問APP2無需要認證
2:用戶在APP1或者APP2點擊注銷,顯示CAS的注銷成功頁面,然后再訪問APP1或者APP2都需要再次認證。

在APP1和APP2的web.xml文件中
CAS Single Sign Out Filter 相關配置就是實現此功能的作用
客戶端訪問 http://localhost:8080/casweb3.5.2/logout 退出

如果直接訪問CAS的logout話,會出現注銷成功頁面,其實大部分情況下這個頁面是沒有必要的,更多的需求可能是退出后顯示登錄頁面,并且登錄成功后還是會進入到之前的業務系統,那么可以修改cas-servlet.xml文件,在"logoutController"的bean配置中增加屬性 “followServiceRedirects”,設置為“true”
然后在業務系統的注銷連接中加入"service參數",值為業務系統的絕對URL,這樣就OK了,如你的業務系統URL 為:http://localhost:8080/casClient,那么注銷URL就為:http://localhost:8080/cas /logout?service=http://localhost:8080/casClient
 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!