cas(三)之数据源
在默认的cas服务端访问,之前说过使用一样的用户名和密码即可登录成功,但是生产上的用户名和密码都是已经被保存好的,有些在LDAP里面,而有些在数据库里面,下面就介绍cas服务端连接数据库来获取动态的用户名和密码,对应的配置在deployerConfigContext.xml文件里面,下面介绍cas集成数据源实现通过查询数据库来确定用户名密码是否正确。
- 集成cas-server支持jdbc模块和mysql驱动
<properties> <mysql.version>5.1.21</mysql.version> <druid.version>1.0.23</druid.version> </properties> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> </dependencies>
我采用druid的数据库连接池。
- 修改deployerConfigContext.xml配置文件,更改用户名密码认证查询方式
先新增一个数据源
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 基本属性 url、user、password --> <property name="url" value="jdbc:mysql://ip:port/database?useUnicode=true&characterEncoding=utf-8" /> <property name="username" value="username" /> <property name="password" value="password" /> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="5" /> <property name="minIdle" value="5" /> <property name="maxActive" value="50" /> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000" /> <property name="validationQuery" value="SELECT 'x'" /> <property name="testWhileIdle" value="true" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 --> <property name="poolPreparedStatements" value="false" /> <property name="maxPoolPreparedStatementPerConnectionSize" value="20" /> <!-- 配置监控统计拦截的filters --> <property name="filters" value="stat" /> </bean>
然后在文件找到authenticationHandlers,将org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler注释掉,然后新增下面内容
<bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler"> <property name="dataSource" ref="dataSource" /> <!-- 密码加密器--> <property name="passwordEncoder" ref="passwordEncoder"/> <!-- 表名字段--> <property name="tableUsers" value="sys_user"/> <!-- 用户名字段--> <property name="fieldUser" value="account"/> <!-- 密码字段--> <property name="fieldPassword" value="`PASSWORD`" /> </bean>
至此,登录用户名和密码查询数据库集成成功。下面介绍一下数据库查询用户名和密码几个类
- 默认的SimpleTestUsernamePasswordAuthenticationHandler
这个逻辑就是cas给的默认逻辑,也就是上面所说的用户名和密码一致便可成功登陆系统,通过下面的源代码很清楚的知道
public final class SimpleTestUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { public SimpleTestUsernamePasswordAuthenticationHandler() { log.warn(this.getClass().getName() + " is only to be used in a testing environment. NEVER enable this in a production environment."); } public boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) { final String username = credentials.getUsername(); final String password = credentials.getPassword(); //用户名和密码都不为空,并且相等返回true if (StringUtils.hasText(username) && StringUtils.hasText(password) && username.equals(getPasswordEncoder().encode(password))) { log.debug("User [" + username + "] was successfully authenticated."); return true; } log.debug("User [" + username + "] failed authentication"); return false; } }
- AbstractJdbcUsernamePasswordAuthenticationHandler
AbstractJdbcUsernamePasswordAuthenticationHandler处理是jdbc用户名密码验证接口的父类,先讲它为后面讲其他两个打下基础,也更好去理解。通过源码知道,该Handler只是提供了设置DataSource的方式,也就是提供了连接数据库的方式,通过datasource新建一个JdbcTemplate,就可以方便的查询数据库。源码如下
public abstract class AbstractJdbcUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { @NotNull private SimpleJdbcTemplate jdbcTemplate; @NotNull private DataSource dataSource; /** * Method to set the datasource and generate a JdbcTemplate. * * @param dataSource the datasource to use. */ public final void setDataSource(final DataSource dataSource) { this.jdbcTemplate = new SimpleJdbcTemplate(dataSource); this.dataSource = dataSource; } /** * Method to return the jdbcTemplate * * @return a fully created JdbcTemplate. */ protected final SimpleJdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } protected final DataSource getDataSource() { return this.dataSource; } }
当需要验证用户名密码的时候,会调用父类(AbstractUsernamePasswordAuthenticationHandler)的doAuthentication方法,也就会调用子类的authenticateUsernamePasswordInternal方法,代码如下,下面省略了部分代码
public abstract class AbstractUsernamePasswordAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler { /** * Method automatically handles conversion to UsernamePasswordCredentials * and delegates to abstract authenticateUsernamePasswordInternal so * subclasses do not need to cast. */ protected final boolean doAuthentication(final Credentials credentials) throws AuthenticationException { return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials); } /** * Abstract convenience method that assumes the credentials passed in are a * subclass of UsernamePasswordCredentials. * * @param credentials the credentials representing the Username and Password * presented to CAS * @return true if the credentials are authentic, false otherwise. * @throws AuthenticationException if authenticity cannot be determined. */ protected abstract boolean authenticateUsernamePasswordInternal( final UsernamePasswordCredentials credentials) throws AuthenticationException; }
- SearchModeSearchDatabaseAuthenticationHandler
这个类(继承了AbstractJdbcUsernamePasswordAuthenticationHandler)只需要提供表名、用户名字段、密码字段,通过替换SQL字符串,来查询数据库里的用户名和密码
public class SearchModeSearchDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler implements InitializingBean { private static final String SQL_PREFIX = "Select count('x') from "; @NotNull private String fieldUser; @NotNull private String fieldPassword; @NotNull private String tableUsers; private String sql; protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { final String transformedUsername = getPrincipalNameTransformer().transform(credentials.getUsername()); final String encyptedPassword = getPasswordEncoder().encode(credentials.getPassword()); final int count = getJdbcTemplate().queryForInt(this.sql, transformedUsername, encyptedPassword); return count > 0; } public void afterPropertiesSet() throws Exception { this.sql = SQL_PREFIX + this.tableUsers + " Where " + this.fieldUser + " = ? And " + this.fieldPassword + " = ?"; } /** * @param fieldPassword The fieldPassword to set. */ public final void setFieldPassword(final String fieldPassword) { this.fieldPassword = fieldPassword; } /** * @param fieldUser The fieldUser to set. */ public final void setFieldUser(final String fieldUser) { this.fieldUser = fieldUser; } /** * @param tableUsers The tableUsers to set. */ public final void setTableUsers(final String tableUsers) { this.tableUsers = tableUsers; } }
- QueryDatabaseAuthenticationHandler
这个类(也继承了AbstractJdbcUsernamePasswordAuthenticationHandler),只需要提供预编译型的查询密码的SQL,通过jdbcTemplate可以查询出用户名对应的密码即可,源代码如下:
public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { @NotNull private String sql; protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { final String username = getPrincipalNameTransformer().transform(credentials.getUsername()); final String password = credentials.getPassword(); final String encryptedPassword = this.getPasswordEncoder().encode( password); try { final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username); return dbPassword.equals(encryptedPassword); } catch (final IncorrectResultSizeDataAccessException e) { // this means the username was not found. return false; } } /** * @param sql The sql to set. */ public void setSql(final String sql) { this.sql = sql; } }

浙公网安备 33010602011771号