在前幾篇中有簡單介紹服務端的認證方式,默認的是直接在 deployerConfigContext.xml 文件中 一個叫做 primaryAuthenticationHandler 的bean中配置。不過這只支持一個賬號,而且是固定的,這有非常大的局限性,在現實系統中是肯定不能用這樣的方式的。
現在的應用系統一般都是通過讀取數據庫的方式驗證用戶名、密碼是否正確,進而進行認證的。因此在這一篇文章中將會介紹,怎么把服務端的默認認證方式改造成數據庫驗證的方式,以此滿足系統的基本需求。
1.增加數據源配置
數據源的配置和平常我們配置的差不多,CAS可以使用Spring方式進行配置,為了和原來的配置文件分開,我新建了一個叫做 applicationContext-datasource.xml 的配置用來存放數據源的相關配置,(放在cas-server-webapp\src\main\webapp\WEB-INF\spring-configuration下)具體如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd"> <description>datasource</description> <bean id="casDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <property name="driverClassName" value="${driverClassName}" /> <property name="maxActive" value="${maxActive}" /> <property name="initialSize" value="${initialSize}" /> <property name="maxWait" value="${maxWait}" /> <property name="minIdle" value="${minIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${validationQuery}" /> <property name="testWhileIdle" value="${testWhileIdle}" /> <property name="testOnBorrow" value="${testOnBorrow}" /> <property name="testOnReturn" value="${testOnReturn}" /> <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" /> <property name="removeAbandoned" value="${removeAbandoned}" /> <!-- 打開removeAbandoned功能 --> <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" /> <!-- 1800秒,也就是30分鍾 --> <property name="logAbandoned" value="${logAbandoned}" /> <!-- 關閉abanded連接時輸出錯誤日志 --> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="casDataSource" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="casDataSource" /> <!-- 通過AOP配置提供事務增強,讓AccountService下所有Bean的所有方法擁有事務 --> <aop:config> <aop:pointcut id="serviceMethod" expression=" execution(* com.blog.cas.account.service.impl..*(..))" /> <aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="update*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <bean id="accountService" class="com.blog.cas.account.service.impl.AccountServiceImpl" p:accountDao-ref="accountDao" /> <bean id="accountDao" class="com.blog.cas.account.dao.impl.AccountDaoImpl" p:jdbcTemplate-ref="jdbcTemplate" /> </beans>
注:在這邊有定義一個Service、一個Dao ,是用來與數據庫進行交互的,里面大家寫自己需要的方法就行了。這邊使用的Spring提供的JdbcTemplate 進行查詢。這兩個類就不貼出來了,大家自由實現
然后數據源的相關信息,我直接放在文件 cas.properties(cas-server-webapp\src\main\webapp\WEB-INF\cas.properties), 在最后增加下面的內容:
##
# Jdbc
url=jdbc:oracle:thin:@192.168.1.101:1521:odsorcl
username=blog
password=blog
driverClassName=oracle.jdbc.driver.OracleDriver
validationQuery=SELECT 1 from dual
filters=stat
maxActive=20
initialSize=1
maxWait=60000
minIdle=10
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
maxOpenPreparedStatements=20
removeAbandoned=true
removeAbandonedTimeout=1800
logAbandoned=true
用的是oracle數據庫。
2.自定義認證Handler類
cas 默認使用的認證類為 org.jasig.cas.authentication.AcceptUsersAuthenticationHandler。我們看看它的源碼,發現認證是在一個叫做 authenticateUsernamePasswordInternal 的方法中進行的,其實看方法名大家都能猜出來這個方法是做什么的。然后這個類的父類是AbstractUsernamePasswordAuthenticationHandler ,那么我們也繼承這個類,實現authenticateUsernamePasswordInternal方法其實就可以了。
這邊還要注意一下,authenticateUsernamePasswordInternal 方法中的參數是一個 UsernamePasswordCredential 類型的參數,里面其實包含了我們在頁面上輸入的用戶相關信息,即用戶名和密碼。好了知道方法了,那么就動手吧。
public class BlogUsersAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { private AccountServiceImpl accountService; @Override protected HandlerResult authenticateUsernamePasswordInternal( UsernamePasswordCredential credential) throws GeneralSecurityException, PreventedException { String username = credential.getUsername(); String password = credential.getPassword(); boolean flag = accountService.checkAccount(username, password); if (!flag) { throw new FailedLoginException(); } return createHandlerResult(credential, new SimplePrincipal(username), null); } //省略get/set 方法 }
這邊只是一個簡單的驗證邏輯,實際上可能會復雜點,比如判斷用戶的狀態、是否禁用等等。
然后修改相關的配置,打開文件 cas-server-webapp\src\main\webapp\WEB-INF\deployerConfigContext.xml 找到 id 為 primaryPrincipalResolver 的bean ,把這個修改成我們新增的類
<!-- <bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="admin" value="admin"/> </map> </property> </bean> --> <bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.BlogUsersAuthenticationHandler"> <property name="accountService" ref="accountService" /> </bean>
好了,現在已經修改成通過數據庫認證的方式了,大家可以試試看。
3.認證流程
看完了上面的,大家可能會覺得有點不理解。為什么只加了一個類,覆蓋了其中的方法就完成了認證呢,在這節中介紹下大概的一個認證流程,以及到最終的信息返回給客戶端的這樣的一個流程。
其中一些內容在第四篇中有介紹了,最好先了解下第四篇 登錄后用戶信息的返回 相關的內容, 傳送門
- 用戶登錄頁輸入相關信息,點擊submit登陸
- 執行AuthenticationViaFormAction 類中的 submit 方法中
- 在submit方法中 調用 CentralAuthenticationService 類的grantServiceTicket 方法,其中有傳入 Credential 類型的參數
- 接着在 grantServiceTicket方法中 調用了 AuthenticationManager類(實際類為PolicyBasedAuthenticationManager)的authenticate 方法
- authenticate方法中又調用了authenticateInternal 方法
- 最終在 authenticateInternal 方法中調用了AuthenticationHandler 的authenticate 的方法 ,authenticate 方法就會調用我們上面自定義的 BlogUsersAuthenticationHandler 方法
- 然后根據我們寫的方法返回的相關信息調用 resolvePrincipal 方法,把credential 類型的信息轉變為Principal類型的信息,即組裝我們需要返回給客戶端的一些信息。這個主要是通過 PrincipalResolver 類進行轉變得,第四篇中有重點說到,這邊就不細講了。
- 最終成功登陸到客戶端
上面的流程只是認證的主要流程,不包含ST的生成、驗證等過程。
4.總結
通過數據庫認證基本上寫完了,不過上面只是簡單的演示了下,大家需要根據自己的情況進行修改。
如果上面的內容有錯誤,歡迎大家指出。也歡迎大家多多留言。大家共同進步。
順便祝大家情人節快樂,新年快樂。
打完收工...