【SSO单点系列】(7):CAS4.0 SERVER通过数据库方式认证用户

  在前几篇中有简单介绍服务端的认证方式,默认的是直接在 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.认证流程

看完了上面的,大家可能会觉得有点不理解。为什么只加了一个类,覆盖了其中的方法就完成了认证呢,在这节中介绍下大概的一个认证流程,以及到最终的信息返回给客户端的这样的一个流程。

其中一些内容在第四篇中有介绍了,最好先了解下第四篇 登录后用户信息的返回 相关的内容, 传送门

  1. 用户登录页输入相关信息,点击submit登陆
  2. 执行AuthenticationViaFormAction 类中的  submit 方法中
  3. 在submit方法中 调用 CentralAuthenticationService 类的grantServiceTicket 方法,其中有传入 Credential 类型的参数
  4. 接着在 grantServiceTicket方法中 调用了 AuthenticationManager类(实际类为PolicyBasedAuthenticationManager)的authenticate 方法
  5. authenticate方法中又调用了authenticateInternal 方法
  6. 最终在 authenticateInternal 方法中调用了AuthenticationHandler 的authenticate 的方法 ,authenticate 方法就会调用我们上面自定义的 BlogUsersAuthenticationHandler  方法
  7. 然后根据我们写的方法返回的相关信息调用 resolvePrincipal 方法,把credential 类型的信息转变为Principal类型的信息,即组装我们需要返回给客户端的一些信息。这个主要是通过 PrincipalResolver 类进行转变得,第四篇中有重点说到,这边就不细讲了。
  8. 最终成功登陆到客户端

上面的流程只是认证的主要流程,不包含ST的生成、验证等过程。

4.总结

通过数据库认证基本上写完了,不过上面只是简单的演示了下,大家需要根据自己的情况进行修改。

如果上面的内容有错误,欢迎大家指出。也欢迎大家多多留言。大家共同进步。

顺便祝大家情人节快乐新年快乐

打完收工...

上一篇:自学Zabbix10.1 Configuration export/import 配置导入导出


下一篇:Spark1.0.0 开发环境高速搭建