CAS單點登錄源碼解析之【用戶認證】

前期準備

已經搭建好了集成了CAS客戶端的應用系統和CAS服務器

1.應用系統webapp(http://127.0.0.1:8090/webapp/main.do)
2.CAS單點登錄服務器端(http://127.0.0.1:8081/cas-server/)

        本次討論包括CAS單點登錄服務器端的部分源碼,以及在此基礎上進行用戶認證二次開發,因此需要修改部分CAS服務器端的源碼,源碼部分的修改在下面進行討論。關於CAS客戶端、CAS服務器端和CAS單點登出的源碼分析,請參考另外三篇文章

CAS客戶端:http://blog.csdn.net/dovejing/article/details/44426547
CAS服務器端:http://blog.csdn.net/dovejing/article/details/44523545
CAS單點登出:http://blog.csdn.net/dovejing/article/details/44675647

deployerConfigContext.xml部分代碼

<bean id="dataSource"  class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="com.mysql.jdbc.Driver" />
	<property name="url" value="jdbc:mysql://127.0.0.1:3306/MASTER?useUnicode=true&characterEncoding=UTF-8" />
	<property name="username" value="root" />
	<property name="password" value="root" />
	<property name="initialSize" value="5"/>
	<property name="maxActive" value="10" />
	<property name="maxIdle" value="100" />
	<property name="maxWait" value="1000" />
	<property name="timeBetweenEvictionRunsMillis" value="1000"/>
	<property name="testWhileIdle" value="true"/>
	<property name="validationQuery" value="select 1"/>
</bean>

CAS服務器端的用戶認證,默認採用SimpleTestUsernamePasswordAuthenticationHandler類,當輸入的用戶名和密碼相同時,視爲認證通過。但實際情況我們會根據需求需要增加自己的用戶認證功能,首先在deployerConfigContext.xml配置文件中增加數據源的配置信息(mysql)。

deployerConfigContext.xml部分代碼

<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">	
	<property name="credentialsToPrincipalResolvers">
		<list>
			<bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" >
				<property name="attributeRepository" ref="attributeRepository" />
			</bean>

			<bean class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
		</list>
	</property>

	<property name="authenticationHandlers">
		<list>
			<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
				p:httpClient-ref="httpClient" />
			
			<!-- 註解默認的認證方式 -->
			<!--bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/-->
			<!-- 此處爲增加部分 start -->
			<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
				<property name="sql" value="SELECT PASSWORD FROM USER"/>
				<!-- 引用數據源 -->
				<property name="dataSource" ref="dataSource" />
				<!-- 定義MD5的加密方式 -->
				<property name="passwordEncoder">
					<bean class="org.jasig.cas.authentication.handler.ext.MD5PasswordEncoder"></bean>
				</property>
			</bean>
			<!-- 此處爲增加部分 end -->
		</list>
	</property>
</bean>
<!-- 註解默認的屬性
<bean id="attributeRepository" class="org.jasig.services.persondir.support.StubPersonAttributeDao">
	<property name="backingMap">
		<map>
			<entry key="uid" value="uid" />
			<entry key="eduPersonAffiliation" value="eduPersonAffiliation" /> 
			<entry key="groupMembership" value="groupMembership" />
		</map>
	</property>
</bean>
-->
<!-- 此處爲增加部分 start -->
<bean  class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" id="attributeRepository">
	<constructor-arg index="0" ref="dataSource"/>
	<constructor-arg index="1" value="SELECT * FROM USER WHERE {0}"/>
	<property name="queryAttributeMapping">
		<map>
			<!-- key對應登錄信息, vlaue對應數據庫字段 -->
			<entry key="username" value="LOGIN_NAME"/>
		</map>
	</property>
	<property name="resultAttributeMapping">
		<map>
			<!-- key對應數據庫字段  value對應attribute中的key -->
			<entry key="mobile" value="mobile"/>
			<entry key="email" value="email"/>
		</map>
	</property>
</bean>
<!-- 此處爲增加部分 end -->

<bean id="serviceRegistryDao" class="com.uws.uaserver.services.InMemoryServiceRegistryDaoImpl">
	<property name="registeredServices"> -->
		<!-- 註解默認的配置
		<list>
			<bean class="org.jasig.cas.services.RegexRegisteredService">
				<property name="id" value="0" />
				<property name="name" value="HTTP and IMAP" />
				<property name="description" value="Allows HTTP(S) and IMAP(S) protocols" />
				<property name="serviceId" value="^(https?|imaps?)://.*" />
				<property name="evaluationOrder" value="10000001" />
			</bean>

			<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?)://([A-Za-z0-9_-]+\.)*example\.com/.*" />
				<property name="evaluationOrder" value="0" />
			</bean>
		</list>
		-->
	</property>
</bean>

/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp

<%@ page session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
	<cas:authenticationSuccess>
		<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
		<!-- 增加部分 start -->
		<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>
		<!-- 增加部分 end -->
<c:if test="${not empty pgtIou}">
		<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
		<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
			<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
		</cas:proxies>
</c:if>
	</cas:authenticationSuccess>
</cas:serviceResponse>

修改deployerConfigContext.xml配置文件,註解默認的認證配置和默認的attributeRepository配置信息,增加用戶自己的認證配置和attributeRepository配置。

  1. 初始化QueryDatabaseAuthenticationHandler類的sql屬性(SELECT PASSWORD FROM USER),引用數據源dataSource(mysql數據源),初始化passwordEncode屬性(MD5PasswordEncoderr密碼加密類)。此處的sql屬性只是一個簡單的查詢語句,實際應用中可以定義一個複雜的SQL語句。
  2. 增加自己的attributeRepository配置。dataScore和SELECT * FROM USER WHERE {0}爲SingleRowJdbcPersonAttributeDao構造方法的兩個參數,queryAttributeMapping是爲了組裝SQL(SELECT * FROM USER WHERE LOGIN_NAME=username),resultAttributeMapping是SQL查詢返回結果屬性。

修改casServiceValidationSuccess.jsp頁面,由於默認的頁面只有user的信息,並沒有attributes的信息,因此我們需要增加該信息,本文中只增加了mobile和email,實際應用中可根據需要增加多個屬性信息。

該頁面最終的返回結果如下:

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
	<cas:authenticationSuccess>
		<cas:user>system</cas:user>
		<cas:attributes>
			<cas:mobile>13688888888</cas:mobile>
			<cas:email>[email protected]</cas:email>
		</cas:attributes>
	</cas:authenticationSuccess>
</cas:serviceResponse>

客戶端獲取用戶認證的信息的代碼

AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();  
String loginName = principal.getName();
Map<String, Object> attributes = principal.getAttributes();  
if (attributes != null) { 
	String mobile = attributes.get("mobile"));  
	String email = attributes.get("email"));   
}

QueryDatabaseAuthenticationHandler的authenticateUsernamePasswordInternal方法

protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials)
	throws AuthenticationException {
	//用戶名
	final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
	//密碼
	final String password = credentials.getPassword();

	Map resultMap = null;
	try {
		resultMap = getJdbcTemplate().queryForMap(this.sql, new Object[] { username });
		String dbPassword = (resultMap.get("PASSWORD") == null) ? ""
				: resultMap.get("PASSWORD").toString();
		String encryptedPassword = getPasswordEncoder().encode(password);
			
		//判斷密碼是否相等
		return dbPassword.equals(encryptedPassword);
	} catch (IncorrectResultSizeDataAccessException e) {
	}
	return false;
}
QueryDatabaseAuthenticationHandler的authenticateUsernamePasswordInternal方法,要做的就是獲取用戶輸入的用戶名和密碼,用配置文件中配置的加密方式(MD5PasswordEncoder類)進行加密,用加密後的密碼encryptedPassword和用SQL語句在數據庫中查詢的密碼(dbPassword)進行對比,並返回結果。

MD5PasswordEncoder類

public class MD5PasswordEncoder implements PasswordEncoder {
	public String encode(String password) {
		try {
			return MD5.crypt(password);//MD5加密
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}
}

MD5PasswordEncoder類,要做的就是對用戶輸入的密碼進行加密。此類是新增加的類,用戶可以增加任何有加密邏輯的類,但此類必須實現org.jasig.cas.authentication.handler.PasswordEncoder接口,並實現String encode(String password)方法

至此,CAS用戶認證的修改已經完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章