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


      在前几篇中有简单介绍服务端的认证方式,默认的是直接在 deployerConfigContext.xml 文件中 一个叫做 primaryAuthenticationHandler 的bean中配置。不过这只支持一个账号,而且是固定的,这有非常大的局限性,在现实系统中是肯定不能用这样的方式的。

    现在的应用系统一般都是通过读取数据库的方式验证用户名、密码是否正确,进而进行认证的。因此在这一篇文章中将会介绍,怎么把服务端的默认认证方式改造成数据库验证的方式,以此满足系统的基本需求。

    1.增加数据源配置

    数据源的配置和平常我们配置的差不多,CAS可以使用Spring方式进行配置,为了和原来的配置文件分开,我新建了一个叫做 applicationContext-datasource.xml 的配置用来存放数据源的相关配置,(放在cas-server-webappsrcmainwebappWEB-INFspring-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-webappsrcmainwebappWEB-INFcas.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-webappsrcmainwebappWEB-INFdeployerConfigContext.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 方法中调用了AuthenticationHandlerauthenticate 的方法 ,authenticate 方法就会调用我们上面自定义的 BlogUsersAuthenticationHandler  方法
    7. 然后根据我们写的方法返回的相关信息调用 resolvePrincipal 方法,把credential 类型的信息转变为Principal类型的信息,即组装我们需要返回给客户端的一些信息。这个主要是通过 PrincipalResolver 类进行转变得,第四篇中有重点说到,这边就不细讲了。
    8. 最终成功登陆到客户端

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

    4.总结

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

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

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

    打完收工...

  • 相关阅读:
    https 数字证书理解
    linux token作用及原理
    C 语言section作用
    https 证书的生成及C语言实现
    linux 如何用udp实现可靠传输
    elementui 局域网引用 样式缺失问题
    helm 安装 EFK ES Xpack认证
    helm 安装 Apollo
    helm 安装 MinIO 集群
    k8s 安装 canal 服务
  • 原文地址:https://www.cnblogs.com/vhua/p/cas_7.html
Copyright © 2020-2023  润新知