CAS筆記: 部署與測試

CAS 簡介

cas是YALE大學(xué)發(fā)起的一個開源項目, 旨在為web應(yīng)用系統(tǒng)提供一種可靠的單點登錄方法.

它分為server和client端, server端負(fù)責(zé)對用戶的認(rèn)證工作, client端則負(fù)責(zé)處理對客戶端受保護(hù)的資源的訪問請求.

CAS的原理,如圖:

cas-01.jpg

CAS 名詞

  • Service Ticket: 簡稱ST, ST是CAS為用戶簽發(fā)的訪問某一service的票據(jù)。用戶訪問service時,service發(fā)現(xiàn)用戶沒有ST,則要求用戶去CAS獲取ST。用戶向CAS發(fā)出獲取ST的請求,如果用戶的請求中包含cookie,則CAS會以此cookie值為key查詢緩存中有無TGT,如果存在TGT,則用此TGT簽發(fā)一個ST,返回給用戶。用戶憑借ST去訪問service,service拿ST去CAS驗證,驗證通過后,允許用戶訪問資源。

  • Ticket granting ticket: 簡稱TGT. 是cas服務(wù)器為用戶簽發(fā)的登錄票據(jù).擁有了TGT,用戶就可以證明自己在CAS成功登錄過。TGT封裝了Cookie值以及此Cookie值對應(yīng)的用戶信息。用戶在CAS認(rèn)證成功后,CAS生成cookie,寫入瀏覽器,同時生成一個TGT對象,放入自己的緩存,TGT對象的ID就是cookie的值。當(dāng)HTTP再次請求到來時,如果傳過來的有CAS生成的cookie,則CAS以此cookie值為key查詢緩存中有無TGT ,如果有的話,則說明用戶之前登錄過,如果沒有,則用戶需要重新登錄。

  • Ticket granting cookie: 簡稱TGC. 這是一個cookie, 是cas服務(wù)器放到用戶瀏覽器中用以標(biāo)識用戶身份的cookie.

CAS REST服務(wù)部署

stackoverflow參考資料

部署前的準(zhǔn)備

  • 服務(wù)端創(chuàng)建證書
keytool -genkey -alias SomeName -keyalg RSA -keystore d:/your/dir/target.keystore

接著根據(jù)提示輸入相關(guān)信息.在最后,提示輸入密碼時, 務(wù)必記住你輸入的密碼.

  • 服務(wù)端導(dǎo)出證書
keytool -export -file d:/your/dir/target.crt -alias SomeName -keystore d:/your/dir/target.keystore

導(dǎo)出時, 會提示你輸入剛才創(chuàng)建keystore時的密碼.
導(dǎo)出完成后, 生成的target.crt就可以分發(fā)給客戶端的jdk使用了.

  • 客戶端導(dǎo)入證書
keytool -import -keystore %JAVA_HOME%/jre/lib/security/cacerts -file d:/your/dir/target.crt -alias SomeName

提示輸入密碼. 如果出現(xiàn)keytool error: java.io.IOException: Keystore was tampered with, or password was incorrect錯誤, 則使用密碼changeit.

  • 在服務(wù)端tomcat服務(wù)器上應(yīng)用證書
<!-- 務(wù)必注意大小寫 -->
<connector port="8443" protocol="HTTP/1.1" sslenabled="true"
    maxthreads="150" scheme="https" secure="true" 
    clientauth="false" sslprotocol="TLS" 
    keystoreFile="D:/your/dir/target.keystore" 
    keystorePass="yourPassWord">
</connector>
  • 啟動tomcat服務(wù)器, 驗證SSL是否啟用

    訪問地址https://localhost:8443/

生成支持rest的cas.war

新建目錄, 編寫pom.xml, 使用命令mvn clean package生成cas.war

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <parent>
    <groupId>org.jasig.cas</groupId>
    <artifactId>cas-server</artifactId>
    <version>3.4.12</version>
  </parent>

  <modelVersion>4.0.0</modelVersion>
  <groupId>h.usm.my</groupId>
  <artifactId>cas</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <name>HUSM CAS Web Application</name>

  <properties>
    <cas.version>3.4.12</cas.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.jasig.cas</groupId>
      <artifactId>cas-server-webapp</artifactId>
      <version>${cas.version}</version>
      <type>war</type>
      <scope>runtime</scope>
    </dependency>

    <dependency>
     <groupId>org.jasig.cas</groupId>
     <artifactId>cas-server-support-jdbc</artifactId>
     <version>${cas.version}</version>
   </dependency>

    <dependency>
      <groupId>org.jasig.cas</groupId>
      <artifactId>cas-server-integration-restlet</artifactId>
      <version>${cas.version}</version>
      <type>jar</type>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.187</version>
    </dependency>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>${hibernate.core.version}</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>3.6.0.Final</version>
    </dependency>
  </dependencies>

  <repositories>
    <repository>
      <id>ja-sig</id>
      <url>http://oss.sonatype.org/content/repositories/releases</url>
    </repository>
  </repositories>

  <build>
        <plugins>
            <plugin>
                 <artifactId>maven-war-plugin</artifactId>
                             <configuration>
                                 <warName>cas</warName>
                             </configuration>
                        </plugin>
        </plugins>
    </build>
</project>

修改cas.war的web.xml, 填寫Rest Servlet

<!--add a servlet-->
    <servlet>
        <servlet-name>restlet</servlet-name>
        <servlet-class>com.noelios.restlet.ext.spring.RestletFrameworkServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>restlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

修改cas.war的deployerConfigContext.xml, 修改用戶名密碼的驗證方式

  • 注釋掉默認(rèn)的SimpleTestUsernamePasswordAuthenticationHandler

  • 添加新的AuthentitcationHandler

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
    <property name="dataSource" ref="dataSource"></property>
    <property name="sql" value="select password from t_admin_user where login_name=?"></property>
    <property name="passwordEncoder" ref="MD5PasswordEncoder"></property>
</bean>

這里使用了數(shù)據(jù)庫來存儲用戶的帳號與密碼.
驗證時使用sql進(jìn)行查詢,并對查詢獲得的password字段值,與使用MD5PasswordEncoder進(jìn)行加密后的輸入密碼, 進(jìn)行比對驗證.

相關(guān)的dataSource與encoder配置如下:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="com.mysql.jdbc.Driver" />
   <property name="url" value="jdbc:mysql:///wsriademo" />
   <property name="username" value="sa" />
   <property name="password" value="root" />
</bean>
 
<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
    <constructor-arg index="0">
        <value>MD5</value>
    </constructor-arg>
</bean>

通過org.jasig.cas.authentication.handler.PasswordEncoder接口可實現(xiàn)自定義加密類.
記得添加相應(yīng)的數(shù)據(jù)庫驅(qū)動jar包到lib目錄下.

驗證

訪問 https://localhost:8443/cas, 輸入賬密進(jìn)行網(wǎng)頁驗證.

CAS Rest的java驗證代碼


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;

import javax.net.ssl.HttpsURLConnection;

public class TestCasRest {
    
    /**
     * resolve exception:
     *      java.security.cert.CertificateException: No name matching localhost found
     */
    static {
        //for localhost testing only
        javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
        new javax.net.ssl.HostnameVerifier(){
 
            public boolean verify(String hostname,
                    javax.net.ssl.SSLSession sslSession) {
                if (hostname.equals("localhost")) {
                    return true;
                }
                return false;
            }
        });
    }

    public static void main(String... args) throws Exception {
        String username = "alpha";
        String password = "123";
        validateFromCAS(username, password);
    }

    public static boolean validateFromCAS(String username, String password)
            throws Exception {

        String url = "https://localhost:8443/cas/rest/tickets";
        try {
            HttpsURLConnection hsu = (HttpsURLConnection) openConn(url);
            String s = URLEncoder.encode("username", "UTF-8") + "="
                    + URLEncoder.encode(username, "UTF-8");
            s += "&" + URLEncoder.encode("password", "UTF-8") + "="
                    + URLEncoder.encode(password, "UTF-8");

            System.out.println(s);
            OutputStreamWriter out = new OutputStreamWriter(
                    hsu.getOutputStream());
            BufferedWriter bwr = new BufferedWriter(out);
            bwr.write(s);
            bwr.flush();
            bwr.close();
            out.close();

            String tgt = hsu.getHeaderField("location");
            System.out.println("ResponseCode: " + hsu.getResponseCode());
            if (tgt != null && hsu.getResponseCode() == 201) {
                System.out.println(tgt);

                System.out.println("==> TGT is : "
                        + tgt.substring(tgt.lastIndexOf("/") + 1));
                tgt = tgt.substring(tgt.lastIndexOf("/") + 1);
                bwr.close();
                closeConn(hsu);

                String serviceURL = "http://localhost:8080/CasClient";
                String encodedServiceURL = URLEncoder
                        .encode("service", "utf-8")
                        + "="
                        + URLEncoder.encode(serviceURL, "utf-8");
                System.out.println("Service url is : " + encodedServiceURL);

                String myURL = url + "/" + tgt;
                System.out.println(myURL);
                hsu = (HttpsURLConnection) openConn(myURL);
                out = new OutputStreamWriter(hsu.getOutputStream());
                bwr = new BufferedWriter(out);
                bwr.write(encodedServiceURL);
                bwr.flush();
                bwr.close();
                out.close();

                System.out.println("Response code is:  "
                        + hsu.getResponseCode());

                BufferedReader isr = new BufferedReader(new InputStreamReader(
                        hsu.getInputStream()));
                String line;
                System.out.println(hsu.getResponseCode());
                while ((line = isr.readLine()) != null) {
                    System.out.println("==> ST is : " + line);
                }
                isr.close();
                hsu.disconnect();
                return true;

            } else {
                return false;
            }

        } catch (MalformedURLException mue) {
            mue.printStackTrace();
            throw mue;

        } catch (IOException ioe) {
            ioe.printStackTrace();
            throw ioe;
        }

    }

    static URLConnection openConn(String urlk) throws MalformedURLException,
            IOException {

        URL url = new URL(urlk);
        HttpsURLConnection hsu = (HttpsURLConnection) url.openConnection();
        hsu.setDoInput(true);
        hsu.setDoOutput(true);
        hsu.setRequestMethod("POST");
        return hsu;

    }

    static void closeConn(HttpsURLConnection c) {
        c.disconnect();
    }

}

Cas client端(非REST請求方式)的配置

在client端工程添加cas-client-core.jar包及相關(guān)依賴

<dependency>
    <groupid>org.jasig.cas.client</groupid>
    <artifactid>cas-client-core</artifactid>
    <version>3.1.12</version>
</dependency>

修改client端工程的web.xml, 添加cas的過濾器

<!-- 用于單點退出,該過濾器用于實現(xiàn)單點登出功能,可選配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
 
<!-- 該過濾器用于實現(xiàn)單點登出功能,可選配置。 -->
<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>
 
<!-- 該過濾器負(fù)責(zé)用戶的認(rèn)證工作,必須啟用它 -->
<filter>
    <filter-name>CASFilter</filter-name>
    <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>casServerLoginUrl</param-name>
        <param-value>https://sso.wsria.com:8443/cas/login</param-value>
    </init-param>
    <init-param>
        <!--這里的server是服務(wù)端的IP-->
        <param-name>serverName</param-name>
        <param-value>http://localhost:10000</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CASFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
 
<!-- 該過濾器負(fù)責(zé)對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>https://sso.wsria.com:8443/cas</param-value>
    </init-param>
    <init-param>
        <param-name>serverName</param-name>
        <param-value>http://localhost:10000</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>CAS Validation Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
 
<!--
該過濾器負(fù)責(zé)實現(xiàn)HttpServletRequest請求的包裹,
比如允許開發(fā)者通過HttpServletRequest的getRemoteUser()方法獲得SSO登錄用戶的登錄名,可選配置。
-->
<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>
 
<!--
該過濾器使得開發(fā)者可以通過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登錄名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<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>
 
<!-- 自動根據(jù)單點登錄的結(jié)果設(shè)置本系統(tǒng)的用戶信息 -->
<filter>
    <display-name>AutoSetUserAdapterFilter</display-name>
    <filter-name>AutoSetUserAdapterFilter</filter-name>
    <filter-class>com.wsria.demo.filter.AutoSetUserAdapterFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>AutoSetUserAdapterFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<!-- ======================== 單點登錄結(jié)束 ======================== -->

其中自定義的AutoSetUserAdapterFilter的代碼如下

package com.wsria.demo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.wsria.demo.entity.account.User;
import com.wsria.demo.service.account.UserManager;
import com.wsria.demo.util.UserUtil;


/**
 * 自動根據(jù)單點登錄系統(tǒng)的信息設(shè)置本系統(tǒng)的用戶信息
 *
 * @author 咖啡兔
 * @site www.wsria.cn
 *
 */
public class AutoSetUserAdapterFilter implements Filter {
        
        /**
         * Default constructor. 
         */
        public AutoSetUserAdapterFilter() {
        }

        /**
         * @see Filter#destroy()
         */
        public void destroy() {
        }

        /**
         * 過濾邏輯:首先判斷單點登錄的賬戶是否已經(jīng)存在本系統(tǒng)中,
         * 如果不存在使用用戶查詢接口查詢出用戶對象并設(shè)置在Session中
         * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
         */
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
                        ServletException {
                HttpServletRequest httpRequest = (HttpServletRequest) request;
                
                // _const_cas_assertion_是CAS中存放登錄用戶名的session標(biāo)志
                Object object = httpRequest.getSession().getAttribute("_const_cas_assertion_");
                
                if (object != null) {
                        Assertion assertion = (Assertion) object;
                        String loginName = assertion.getPrincipal().getName();
                        User user = UserUtil.getCurrentUser(httpRequest.getSession());
                        
                        // 第一次登錄系統(tǒng)
                        if (user == null) {
                                WebApplicationContext wct = WebApplicationContextUtils.getWebApplicationContext(httpRequest
                                                .getSession().getServletContext());
                                UserManager userManager = (UserManager) wct.getBean("userManager");
                                user = userManager.findUserByLoginName(loginName);
                                // 保存用戶信息到Session
                                UserUtil.saveUserToSession(httpRequest.getSession(), user);
                        }
                        
                }
                chain.doFilter(request, response);
        }

        /**
         * @see Filter#init(FilterConfig)
         */
        public void init(FilterConfig fConfig) throws ServletException {
        }

}

附注

單點退出

訪問https://localhost:8443/cas/logout即可.

美化CAS服務(wù)器界面

修改cas\WEB-INF\view\jsp\default\ui下相關(guān)的jsp文件

在服務(wù)端不使用SSL協(xié)議

  • 修改%CATALINA_HOME%\conf\server.xml文件, 關(guān)閉Tomcat服務(wù)器的SSL端口
<!-- 關(guān)閉SSL端口
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"  
               maxThreads="150" scheme="https" secure="true"  
               clientAuth="false" sslProtocol="TLS"  
               keystoreFile="E:/sso/keys/dcssokey" keystorePass="dcfs00"  
               truststoreFile="D:/ProgramFiles/Java/jdk1.6.0_25/jre/lib/security/cacerts" />  
-->
  • 修改服務(wù)端cas\WEB-INF\deployerConfigContext.xml文件
<!-- 添加非安全協(xié)議配置 -->
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"  
    p:httpClient-ref="httpClient" 
    p:requireSecure="false" />  
  • 修改服務(wù)端的cas\WEB-INF\spring-configuration\ticketGrantingTicketCookieGennerator.xml文件
<!-- 修改cookie非安全協(xié)議配置 -->
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"  
        p:cookieSecure="false"  
        p:cookieMaxAge="600"  
        p:cookieName="CASTGC"  
        p:cookiePath="/cas" />  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容