1. 程式人生 > >cas 配置與自定義開發

cas 配置與自定義開發

1. 下載 cas server 原始碼

https://github.com/Jasig/cas/releases

我下載的是 4.0.1。你也可以直接checkout

cas client : http://downloads.jasig.org/cas-clients/

版本是 3.3.3

2. 將下載的 cas-4.0.1.zip 解壓, 在根目錄 執行 

mvn package install -Dmaven.test.skip=true

執行完成後,可將 cas-server-webapp\target\cas.war 部署到tomcat

3. 生成證書

生成伺服器端證書

keytool -genkey  -v  -alias  tomcat1 -dname "cn=localhost,ou=cas,o=cas,c=CN" -keyalg  RSA  -keystore d:/tomcat1.keystore -storepass 123456   -keypass 123456 -validity 3650

為客戶端生成證書

keytool -genkey  -v   -alias myKey -keyalg   RSA  -storetype PKCS12   -keystore d:/my1.p12   -dname "cn=localhost,ou=cas,o=cas,c=CN"  -storepass 123456   -keypass 123456   -validity 3650

讓伺服器信任客戶端證書
keytool -export -alias myKey   -keystore d:/my1.p12  -storetype PKCS12 -rfc  -file d:/my1.cer

客戶端證書匯入到伺服器的證書庫

keytool -import -v   -file d:/my1.cer  -keystore d:/tomcat1.keystore -storepass 123456

讓客戶端信任伺服器證書

keytool -keystore d:/tomcat1.keystore -export -alias tomcat1 -file d:/tomcat1.cer

在瀏覽器中匯入伺服器和客戶端證書。

雙擊 tomcat1.cer 即可匯入伺服器證書。按照提示安裝證書,將證書填入到“受信任的根證書頒發機構”。

雙擊 my1.p12 即可匯入伺服器證書。按照提示安裝證書,將證書填入到“個人”。


注意,cn要設定為伺服器地址,如 localhost,方便除錯,否則後面cas server 回撥 cas client 會出錯:

java.security.cert.CertificateException: No name matching localhost found

為客戶端的JVM匯入金鑰

keytool -import -file d:/tomcat1.cer -alias tomcat1 -keystore "%java_home%\jre\lib\security\cacerts"


檢視證書
keytool -list -v -keystore "%java_home%\jre\lib\security\cacerts" -alias localhost  

修改 tomcat conf server.xml :

<Connector SSLEnabled="true" clientauth="false" keystoreFile="conf/tomcat1.keystore" keystorePass="123456" maxThreads="150"
                     port="8443" protocol="HTTP/1.1" scheme="https" secure="true" sslProtocol="TLS" />



4. 開發 cas server

第一種:下載 cas server 原始碼後,執行

mvn eclipse:eclipse
匯入eclipse,這時會報錯:
Plugin execution not covered by lifecycle configuration:xxx plugin

解決方法:

在 cas-4.0.1\pom.xml 裡的 build - pluginManagement  - plugins 節點加入:

<plugin>
			<groupId>org.eclipse.m2e</groupId>
			<artifactId>lifecycle-mapping</artifactId>
			<version>1.0.0</version>
			<configuration>
			   <lifecycleMappingMetadata>
				  <pluginExecutions>
				    <pluginExecution>
				      <pluginExecutionFilter>
				        <groupId>org.apache.maven.plugins</groupId>
				        <artifactId>maven-checkstyle-plugin</artifactId>
				        <versionRange>2.10</versionRange>
				        <goals>
				          <goal>checkstyle</goal>
				        </goals>
				      </pluginExecutionFilter>
				      <action>
				        <ignore />
				      </action>
				    </pluginExecution>
				    <pluginExecution>
				      <pluginExecutionFilter>
				        <groupId>com.mycila.maven-license-plugin</groupId>
				        <artifactId>maven-license-plugin</artifactId>
				        <versionRange>1.9.0</versionRange>
				        <goals>
				          <goal>check</goal>
				        </goals>
				      </pluginExecutionFilter>
				      <action>
				        <ignore />
				      </action>
				    </pluginExecution>
				    <pluginExecution>
				      <pluginExecutionFilter>
				        <groupId>org.codehaus.mojo</groupId>
				        <artifactId>aspectj-maven-plugin</artifactId>
				        <versionRange>1.4</versionRange>
				        <goals>
				          <goal>compile</goal>
				        </goals>
				      </pluginExecutionFilter>
				      <action>
				        <ignore />
				      </action>
				    </pluginExecution>
				  </pluginExecutions>
				</lifecycleMappingMetadata>
			</configuration>
		</plugin>


第二種方法:

匯入eclipse,import - maven - existing maven projects,在pom.xml加入依賴,支援訪問資料庫驗證密碼:

<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-core</artifactId>
			<version>${cas.version}</version>
		</dependency>
		<dependency>
			<groupId>org.jasig.cas</groupId>
			<artifactId>cas-server-support-jdbc</artifactId>
			<version>${cas.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.35</version>
		</dependency>
		<dependency>
			<groupId>c3p0</groupId>
			<artifactId>c3p0</artifactId>
			<version>${c3p0.version}</version>
		</dependency>
		        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
	      <groupId>javax.validation</groupId>
	      <artifactId>validation-api</artifactId>
	      <version>${javax.validation.version}</version>
	      <scope>compile</scope>
	    </dependency>

    </dependencies>

    <properties>
        <cas.version>4.0.1</cas.version>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        
        <spring.version>3.2.6.RELEASE</spring.version>
        <javax.validation.version>1.0.0.GA</javax.validation.version>
        <c3p0.version>0.9.1.2</c3p0.version>
    </properties>


修改 deployerConfigContext.xml :

<bean id="authenticationManager" class="org.jasig.cas.authentication.PolicyBasedAuthenticationManager">
        <constructor-arg>
            <map>
               
                <entry key-ref="proxyAuthenticationHandler" value-ref="proxyPrincipalResolver" />
				<!--<entry key-ref="primaryAuthenticationHandler" value-ref="primaryPrincipalResolver" /> -->
				<entry key-ref="dbAuthHandler" value-ref="primaryPrincipalResolver"/>
            </map>
        </constructor-arg>

        
        <property name="authenticationPolicy">
            <bean class="org.jasig.cas.authentication.AnyAuthenticationPolicy" />
        </property>
</bean>

<!-- 
    <bean id="primaryAuthenticationHandler"
          class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
        <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
        </property>
    </bean>
	-->
	<bean id="dataSource"
	  class="com.mchange.v2.c3p0.ComboPooledDataSource"
	 p:driverClass="com.mysql.jdbc.Driver" p:jdbcUrl="jdbc:mysql://localhost:3306/portal_230?useUnicode=true&characterEncoding=UTF8&noAccessToProcedureBodies=true&autoReconnect=true&zeroDateTimeBehavior=convertToNull"
	  p:user="root"
	  p:password="root" />

    <!-- 密碼加密方式-->
	<bean id="passwordEncoder"
      class="com.my.cas.authentication.handler.SelfPasswordEncoder"
      c:encodingAlgorithm="SHA1"
      p:characterEncoding="UTF-8" />

	<bean id="dbAuthHandler"
      class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"
      p:dataSource-ref="dataSource"
      p:sql="select password from test_user where username=? "
      p:passwordEncoder-ref="passwordEncoder"
	  />

其中的

com.my.cas.authentication.handler.SelfPasswordEncoder

為自定義的密碼加密類,實現介面 

org.jasig.cas.authentication.handler.PasswordEncoder

5. 客戶端

引入依賴

<dependency>  
		    <groupId>org.jasig.cas.client</groupId>  
		    <artifactId>cas-client-core</artifactId>  
		    <version>3.2.1</version>  
		</dependency>
在web.xml加入filter
<!-- ======================== 單點登入開始 ======================== -->  
    <!-- 用於單點退出,該過濾器用於實現單點登出功能,可選配置 -->  
    <listener>  
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>  
    </listener>  
  
    <!-- 該過濾器用於實現單點登出功能,可選配置。 -->  
    <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>  
  
    <!-- 該過濾器負責使用者的認證工作,必須啟用它 -->  
    <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.cas.com:8443/cas/login</param-value>  
            <!--這裡的server是服務端的IP -->  
        </init-param>  
        <init-param>  
            <param-name>serverName</param-name>  
            <param-value>http://localhost:8080</param-value><span style="color:#FF0000;"> ①</span>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>CASFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
  
    <!-- 該過濾器負責對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.cas.com:8443/cas</param-value>  
        </init-param>  
        <init-param>  
            <param-name>serverName</param-name>  
            <param-value>http://localhost:8080</param-value>  <span style="color:#FF0000;">②</span>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>CAS Validation Filter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
  
    <!-- 該過濾器負責實現HttpServletRequest請求的包裹, 比如允許開發者通過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>  
  
    <!-- 該過濾器使得開發者可以通過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>  
  
    <!-- ======================== 單點登入結束 ======================== -->  

寫一個controller測試
@Controller
public class TestController {
	@RequestMapping("/test")
	public void test(HttpServletRequest request,
			HttpServletResponse response) throws IOException{
		String username = AssertionHolder.getAssertion().getPrincipal().getName();

		response.getWriter().print("hello, " + username );
		
	}
}

通過 
AssertionHolder.getAssertion().getPrincipal().getName()
 獲取使用者名稱

6. 登入成功後返回更多使用者資訊

cas4.0 沒有了 UsernamePasswordCredentialsToPrincipalResolver,而是提供了 org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver,需要配置 attributeRepository 屬性

    <bean id="primaryPrincipalResolver"
          class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
        <property name="attributeRepository" ref="attributeRepository" />
    </bean>
<bean id="attributeRepository"  class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" >
        <constructor-arg index="0" ref="dataSource"/>
        <constructor-arg index="1" value="select * from test_user where {0}"/>
        <property name="queryAttributeMapping">
            <map>
<!--                 // 這裡的key需寫username,value對應資料庫使用者名稱欄位 -->
                <entry key="username" value="username"/>  
            </map>
        </property>
        <property name="resultAttributeMapping">
            <map>
                <entry key="id" value="id"/>
                <entry key="password" value="password"/>
                <entry key="mobile" value="mobile"/>
                <entry key="email" value="email"/>
            </map>
        </property>
    </bean>

然後將 RegexRegisteredService 的配置改為
<bean class="org.jasig.cas.services.RegexRegisteredService">
            <property name="id" value="1" />
            <property name="name" value="HTTP and IMAP on example.com" />
            <property name="description" value="Allows HTTP(S) and IMAP(S) protocols on example.com" />
            <property name="serviceId" value="^(https?|imaps?)://.*" />
            <property name="evaluationOrder" value="0" />
<!--             <property name="attributeFilter"> -->
<!--               <bean class="org.jasig.cas.services.support.RegisteredServiceRegexAttributeFilter" c:regex="^\w{3}$" />  -->
<!--             </property> -->
<!--             // 客戶端需要使用的物件的屬性名稱 -->
            <property name="allowedAttributes"> 
                                <list>
                                        <value>id</value>
                                        <value>email</value>
                                        <value>mobile</value>
                                </list>
            </property>
        </bean>

最後,修改 WEB-INF\view\jsp\protocol\2.0\casServiceValidationSuccess.jsp,加入
		<c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}">
            <cas:attributes>
                <c:forEach var="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>

客戶端改為

@Controller
public class TestController {
	@RequestMapping("/test")
	public void test(HttpServletRequest request,
			HttpServletResponse response) throws IOException{
		String username = AssertionHolder.getAssertion().getPrincipal().getName();
		AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
		Map<String, Object> attributes = principal.getAttributes();
		String email= (String)attributes.get("email");
		response.getWriter().print("hello, " + username + " . email:" + email);
		
	}
}


搞定!


參考: