CAS的默认配置是使用以下的身份认证处理器进行验证
<bean id="primaryAuthenticationHandler"
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="hugo" value="123456"/>
</map>
</property>
</bean>
身份认证处理器AcceptUsersAuthenticationHandler通过获取预先设定的用户名密码,与从request中获取的用户名密码比对,判定认证是否通过。
但是,这种固定死账号密码的身份认证方式在项目中是不可能使用的,值得庆幸的是,CAS抽象了这个功能,这使得能满足我们对身份认证的各种需求。
在项目中,我们一般通过连接数据库获取用户的用户名和密码,然后认证其身份。接下来我们自定义身份认证的功能实现该需求。
1、添加依赖
除了需要添加MYSQL数据库的驱动、数据库连接池外,我们还需要添加cas的jdbc支持包 cas-server-support-jdbc,它给我们提供了一个抽象类AbstractJdbcUsernamePasswordAuthenticationHandler,它封装了JDBCTemplate和数据库连接池
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
2、建表
# 用户表
create table pms_user
(
`id` bigint not null auto_increment,
`remark` varchar(300) COMMENT '备注',
`login_name` varchar(50) not null COMMENT '登录名',
`login_pwd` varchar(256) not null COMMENT '登录密码',
`salt` varchar(50) not null COMMENT '密码盐值',
primary key (id)
)ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='用户表';
-- -- 用户数据
-- admin 超级管理员
INSERT INTO pms_user (id, remark, login_name, login_pwd, salt)
VALUES (1, '超级管理员', 'admin', '7b0f43981f6b4469aec46395072692b7', 'KZTpzgMKyzQAvNR2SE1URw==');
-- guest 游客
INSERT INTO pms_user (id, remark, login_name, login_pwd, salt)
VALUES (2, '游客', 'guest', '98b67cab3f147441053f98c4ba62c965', 'NH9k1VTIJRb3yuaNMqZSOg==');
3、添加JDBC属性文件
在WEB-INF下添加jdbc.properties文件,并加入以下内容
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/cas-showcase?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
#druid datasource
#参考 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE
druid.initialSize=10
druid.minIdle=10
druid.maxActive=50
druid.maxWait=60000
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 1
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=20
druid.filters=wall,stat
# 身份鉴权器
jdbc.selectSQL=SELECT `login_pwd`,`salt` FROM pms_user WHERE login_name = ?
algorithmName=SHA-1
passwordFieldName=login_pwd
saltFieldName=salt
在propertyFileConfigurer.xml修改添加property-placeholder,引入jdbc属性文件。
<context:property-placeholder ignore-unresolvable="true" properties-ref="casProperties"/> <context:property-placeholder ignore-unresolvable="true" location="/WEB-INF/jdbc.properties"/>
4、自定义身份认证器,并添加配置
其中,passwordFieldName是密码在数据库里的名字,saltFieldName是盐值在数据库中的名字,sql是获取数据库信息的sql语句。这里校验密码的功能的实现因人而异。
public class QueryAndSaltDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
private static final String DEFAULT_PASSWORD_FIELD = "password";
private static final String DEFAULT_SALT_FIELD = "salt";
@NotNull
protected String sql;
@NotNull
protected String passwordFieldName = DEFAULT_PASSWORD_FIELD;
@NotNull
protected String saltFieldName = DEFAULT_SALT_FIELD;
@NotNull
protected String algorithmName;
public QueryAndSaltDatabaseAuthenticationHandler(final DataSource dataSource,
final String sql,
final String algorithmName) {
super();
setDataSource(dataSource);
this.algorithmName = algorithmName;
this.sql = sql;
}
@Override
protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
throws GeneralSecurityException, PreventedException {
final String username = credential.getUsername();
final String encodedPsw = this.getPasswordEncoder().encode(credential.getPassword());
try {
//查询数据库的数据
final Map<String, Object> 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.");
}
} 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);
}
return createHandlerResult(credential, new SimplePrincipal(username, null), null);
}
protected String digestEncodedPassword(final String encodedPassword, final Map<String, Object> values) {
if (!values.containsKey(this.saltFieldName)) {
throw new RuntimeException("Specified field name for salt does not exist in the results");
}
final String salt = values.get(this.saltFieldName).toString();
return PasswordUtils.encryptPassword(encodedPassword, salt, this.algorithmName);
}
public final void setPasswordFieldName(final String passwordFieldName) {
this.passwordFieldName = passwordFieldName;
}
public final void setSaltFieldName(final String saltFieldName) {
this.saltFieldName = saltFieldName;
}
}
接下来需要在deployerConfigContext.xml中配置认证器的bean。
首先,删除或者注释下面默认的认证处理器
<bean id="primaryAuthenticationHandler"
class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
<property name="users">
<map>
<entry key="hugo" value="123456"/>
</map>
</property>
</bean>
添加自定义的身份认证处理器
<!-- 自定义验证处理类 -->
<bean id="primaryAuthenticationHandler"
class="org.jasig.cas.adaptors.jdbc.QueryAndSaltDatabaseAuthenticationHandler">
<constructor-arg name="dataSource" ref="dataSource" />
<constructor-arg name="sql" value="${jdbc.selectSQL}" />
<!-- 加密算法 -->
<constructor-arg name="algorithmName" value="${algorithmName}"/>
<!-- 数据库中密码字段 -->
<property name="passwordFieldName" value="${passwordFieldName}" />
<!-- 数据库中salt字段 -->
<property name="saltFieldName" value="${saltFieldName}" />
</bean>
添加数据源
<!-- 数据源 -->
<!--see https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_DruidDataSource%E5%8F%82%E8%80%83%E9%85%8D%E7%BD%AE-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${druid.initialSize}"/>
<property name="minIdle" value="${druid.minIdle}"/>
<property name="maxActive" value="${druid.maxActive}"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid.maxWait}"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis"
value="${druid.timeBetweenEvictionRunsMillis}"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="${druid.validationQuery}"/>
<property name="testWhileIdle" value="${druid.testWhileIdle}"/>
<property name="testOnBorrow" value="${druid.testOnBorrow}"/>
<property name="testOnReturn" value="${druid.testOnReturn}"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。-->
<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}"/>
<property name="maxPoolPreparedStatementPerConnectionSize"
value="${druid.maxPoolPreparedStatementPerConnectionSize}"/>
<property name="filters" value="wall"/>
</bean>
接下来,启动服务器,一切正常。