JASIG CAS学习(四): 服务端使用JDBC认证

         在之前的认证中,都是以CAS默认的用户名/密码 :casuser/Mellon 来登录,这样肯定是不能满足要求的,用户的信息一般都是会存在数据库中。所以,现在就开始探索一下CAS的JDBC认证方式。关于CAS的JDBC的认证方式,具体信息可以去 CAS官网上查看,依照官网上给出的几种handler及其配置进行选择,我这里选择是QueryAndEncodeDatabaseAuthenticationHandler,该handler可以进行密码的加密以及加盐,更加的安全。

        根据官网的文档,这里先需要添加一个JDBC依赖,以及你需要连接的数据库的依赖,我这里是MYSQL。

        
			org.jasig.cas
			cas-server-support-jdbc
			${cas.version}
		

		
		
			mysql
			mysql-connector-java
			5.1.30
			runtime
		

        然后还需要将一个名为 dataSource的bean添加到Spring中,以及需要将认证处理器修改为指定的的 QueryAndEncodeDatabaseAuthenticationHandler 。这个配置是在 deployerConfigContext.xml 中。在该文件中可以看到这样一行配置   ,意思是当前选择的 handler是 acceptUsersAuthenticationHandler , 然后需要将其切换为  QueryAndEncodeDatabaseAuthenticationHandler 。该acceptUsersAuthenticationHandler 对应的配置就是默认的用户名和密码。

##
# Accepted Users Authentication
#
#accept.authn.users=casuser::Mellon

       首先,将 deployerConfigContext.xml 拷贝到 工程中 WEB-INF 文件夹下 , 然后将 dataSource 的bean 添加进去 ,以及切换 handler。

    
    
    
    
    
    	

	
	

       接着按照官方文档将dataSource的这些属性都添加到 cas.properties 中。对应的 url,user,password都需配置成自己对应的。

# == Basic database connection pool configuration ==
database.driverClass=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8
database.user=root
database.password=123456
database.pool.minSize=6
database.pool.maxSize=18

# Maximum amount of time to wait in ms for a connection to become
# available when the pool is exhausted
database.pool.maxWait=10000

# Amount of time in seconds after which idle connections
# in excess of minimum size are pruned.
database.pool.maxIdleTime=120

# Number of connections to obtain on pool exhaustion condition.
# The maximum pool size is always respected when acquiring
# new connections.
database.pool.acquireIncrement=6

# == Connection testing settings ==

# Period in s at which a health query will be issued on idle
# connections to determine connection liveliness.
database.pool.idleConnectionTestPeriod=30

# Query executed periodically to test health
database.pool.connectionHealthQuery=select 1

# == Database recovery settings ==

# Number of times to retry acquiring a _new_ connection
# when an error is encountered during acquisition.
database.pool.acquireRetryAttempts=5

# Amount of time in ms to wait between successive aquire retry attempts.
database.pool.acquireRetryDelay=2000

         这样 , dataSource就配置好了,接下来需要配置对应的 QueryAndEncodeDatabaseAuthenticationHandler 的属性。首先看一下配置。

##
# JDBC Authentication
#
#cas.jdbc.authn.query.encode.sql=
#cas.jdbc.authn.query.encode.alg=
#cas.jdbc.authn.query.encode.salt.static=
#cas.jdbc.authn.query.encode.password=
#cas.jdbc.authn.query.encode.salt=
#cas.jdbc.authn.query.encode.iterations.field=
#cas.jdbc.authn.query.encode.iterations=
cas.jdbc.authn.query.encode.sql 具体执行的sql语句 必填
cas.jdbc.authn.query.encode.alg 加密的算法 可选,默认SHA-256
cas.jdbc.authn.query.encode.salt.static 私盐 可选
cas.jdbc.authn.query.encode.password 表中哪一个字段是密码 可选,默认passowrd
cas.jdbc.authn.query.encode.salt 表中哪一个字段是盐 必填
cas.jdbc.authn.query.encode.iterations.field 表中哪一个字段是加密次数 可选
cas.jdbc.authn.query.encode.iterations 需要加密的次数 可选,默认0


   

 

 

 

 

 

 

 

 

         这里我需要提一下,在sql中需要将 盐的字段带出来,比如我使用的是用户名作为盐,所以sql中将 use_name 字段也带了出来。

##
# JDBC Authentication
#
cas.jdbc.authn.query.encode.sql=select password,user_name from sys_user where user_name = ?
cas.jdbc.authn.query.encode.alg=SHA-256
# cas.jdbc.authn.query.encode.salt.static=
cas.jdbc.authn.query.encode.password=password
cas.jdbc.authn.query.encode.salt=user_name
# cas.jdbc.authn.query.encode.iterations.field=
cas.jdbc.authn.query.encode.iterations=1

         配置完成之后,需要将用户名和密码都存到数据中进行验证,所以可以先看一下 QueryAndEncodeDatabaseAuthenticationHandler 是怎么样将页面上输入的密码进行加密的。打开这个类看一下具体的实现,会发现这个方法。

    @Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential transformedCredential)
            throws GeneralSecurityException, PreventedException {

        if (StringUtils.isBlank(this.sql) || StringUtils.isBlank(this.algorithmName) || getJdbcTemplate() == null) {
            throw new GeneralSecurityException("Authentication handler is not configured correctly");
        }

        final String username = getPrincipalNameTransformer().transform(transformedCredential.getUsername());
        final String encodedPsw = this.getPasswordEncoder().encode(transformedCredential.getPassword());

        try {
            final Map values = getJdbcTemplate().queryForMap(this.sql, username);
            final String digestedPassword = digestEncodedPassword(encodedPsw, values);

            if (!values.get(this.passwordFieldName).equals(digestedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
            return createHandlerResult(transformedCredential,
                    this.principalFactory.createPrincipal(username), null);

        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            } else {
                throw new FailedLoginException("Multiple records found for " + username);
            }
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }

    }

          可以看到 username 和 encodedPsw 就是 输入的用户名和密码 , 那么既然是 encoedPsw 就说明是经过修改了的密码,他是由 一个PasswordEncoder对象调用其encode方法得来的 ,PasswordEncoder是一个接口,有两个实现类 DefaultPasswordEncoder 和 PlainTextPasswordEncoder ,但是其实默认使用的是 PlainTextEncoder,顾名思义就是明文密码。然后通过执行sql获取一个map,将明文密码和map传入 digestEncodedPassword方法

    /**
     * Digest encoded password.
     *
     * @param encodedPassword the encoded password
     * @param values the values retrieved from database
     * @return the digested password
     */
    protected String digestEncodedPassword(final String encodedPassword, final Map values) {
        final ConfigurableHashService hashService = new DefaultHashService();

        if (StringUtils.isNotBlank(this.staticSalt)) {
            hashService.setPrivateSalt(ByteSource.Util.bytes(this.staticSalt));
        }
        hashService.setHashAlgorithmName(this.algorithmName);

        Long numOfIterations = this.numberOfIterations;
        if (values.containsKey(this.numberOfIterationsFieldName)) {
            final String longAsStr = values.get(this.numberOfIterationsFieldName).toString();
            numOfIterations = Long.valueOf(longAsStr);
        }

        hashService.setHashIterations(numOfIterations.intValue());
        if (!values.containsKey(this.saltFieldName)) {
            throw new RuntimeException("Specified field name for salt does not exist in the results");
        }

        final String dynaSalt = values.get(this.saltFieldName).toString();
        final HashRequest request = new HashRequest.Builder()
                                    .setSalt(dynaSalt)
                                    .setSource(encodedPassword)
                                    .build();
        return hashService.computeHash(request).toHex();
    }

         从这里也可以看到,如果我们的sql中没有将盐查出来,是会抛出一个 运行时异常的,自然认证就是无法通过的了。但是这里主要关注的就是如何加密,很明显就是获取一个 HashRequest 对象,然后将该对象传给 DefaultHashService 的 computeHash方法,之后再调用 toHex 方法就得到了加密后的方法。HashRequest 和 DefaultHashService这两个对象都位于org.apache.shiro.crypto.hash 包下,CAS4.2.7版本使用的是 shiro 1.2.6版本 ,所以我们可以新建一个简单的java工程然后将 shiro-core-1.2.6 的 jar扔到 classpath下就可以使用了。

 
    
    public static void main(String[] args) {
		DefaultHashService hashService = new DefaultHashService();
        hashService.setHashAlgorithmName("SHA-256");
        hashService.setHashIterations(1);
        String dynaSalt = "tadmin";
        final HashRequest request = new HashRequest.Builder()
                                    .setSalt(dynaSalt)
                                    .setSource("admin")
                                    .build();
		System.out.println("salted pass : "+hashService.computeHash(request).toHex());
	}

       按照 我在 cas.properties 配置文件中的设置一样,采用SHA-256作为算法,迭代1次,盐是 tadmin,就是我的用户名,admin 是明文密码,将加密出来的密码同用户名 tadmin 一起 insert到 用户表中,重启CAS服务端。使用 tadmin / admin,作为用户名/密码登录 , 登录成功。

你可能感兴趣的:(JASIG CAS学习(四): 服务端使用JDBC认证)