项目使用Yale CAS+Spring Security实现单点登录以及权限验证
需要对Yale CAS的登录模块进行一下改动,如果用户输入帐号密码失败次数超过3次的时候,要把帐号锁定,这样要等管理员解锁以后才可以再次登录这个帐号。
Yale CAS的登录是个典型的密码验证模块,不能提供上述需求,这就需要对CAS的登录部分做些改动
(暂且不考虑这个需求是不是合理,单纯从技术角度来实现这个需求)
具体分析如下:
综合上述分析,实现如下:
总结:这个简单的客户化的过程主要依赖spring的jdbc包来实现数据访问,因此需要对spring以及org.springframework.jdbc.core这个包有所了解,用到的核心类是JdbcTemplate,这个类非常强大,提供了各种数据访问的方法,避免直接写JDBC管理连接、执行查询的繁琐,而又省去了配置hibernate或者JPA等第三方持久化框架的麻烦。
通过这个小例子,可以基本了解了Yale CAS的登录模块的实现。虽然Yale CAS真正强大之处在于它的SSO,以及超级复杂的文件配置(不过比起Spring Security的配置来,还是小巫见大巫啦)
需要对Yale CAS的登录模块进行一下改动,如果用户输入帐号密码失败次数超过3次的时候,要把帐号锁定,这样要等管理员解锁以后才可以再次登录这个帐号。
Yale CAS的登录是个典型的密码验证模块,不能提供上述需求,这就需要对CAS的登录部分做些改动
(暂且不考虑这个需求是不是合理,单纯从技术角度来实现这个需求)
具体分析如下:
- 1. 一个帐户需要多记录两个属性:密码输错的次数以及帐户是否可登录;
- 2. 用户提交登录申请的时候,先检验该帐户是否可登录,如果是,继续进行下面的,如果否,返回登录失败信息;
- 3. 校验用户帐户和密码,如果密码正确,返回登录成功,如果密码错误,继续进行下面的;
- 4. 该帐户的密码输错次数加1,并比较现在的输错次数是否小于3(可配置),如果是,返回登录失败信息,如果否,继续进行下面的;
- 5. 将帐户是否可登录属性设置为否,返回登录失败信息。
综合上述分析,实现如下:
- 1. 数据库中设计帐户信息时加两个字段failureTimes和isValid来记录错误登录次数和是否被锁定
- 2. 重写CAS的密码校验模块,
package com.cas; import org.inspektr.common.ioc.annotation.NotNull; import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler; import org.jasig.cas.authentication.handler.AuthenticationException; import org.jasig.cas.authentication.handler.BadPasswordAuthenticationException; import org.jasig.cas.authentication.handler.UnknownUsernameAuthenticationException; import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; /** * Class that if provided a query that returns a password (parameter of query must be username) will compare that * password to a translated version of the password provided by the user. If they match, then authentication succeeds. * Default password translator is plaintext translator. * * @Date 2009-5-23 */ public class JdbcUsernamePasswordAuthHandlerImpl extends AbstractJdbcUsernamePasswordAuthenticationHandler { // it's better to move below properties to external configure file, for example 'maxFailureTimes' private static final String QUERY_USER_SQL = "select * from user_info where username = ?"; private static final String FAILURE_TRIGGER_SQL = "update user_info set failureTimes = ? where username = ?"; private static final String LOCK_USER_SQL = "update user_info set failureTimes = ?, isValid = ? where username = ?"; @NotNull private String maxFailureTimes; /** * @param paraMaxFailureTimes * the maxFailureTimes to set */ public void setMaxFailureTimes(String paraMaxFailureTimes) { this.maxFailureTimes = paraMaxFailureTimes; } /** * authenticate username password internal * * @param credentials * credentials * @throws AuthenticationException * AuthenticationException * @return true if user login success * @see org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler * #authenticateUsernamePasswordInternal(org.jasig.cas.authentication.principal.UsernamePasswordCredentials) */ @Override protected boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { final String username = credentials.getUsername(); final String password = credentials.getPassword(); JdbcTemplate template = new JdbcTemplate(getDataSource()); try { // get user info by username, if no result found, auto throw IncorrectResultSizeDataAccessException UserInfo userInfo = (UserInfo) template.queryForObject(QUERY_USER_SQL, new String[]{username}, new BeanPropertyRowMapper(UserInfo.class)); // check user lock if (!"Y".equalsIgnoreCase(userInfo.getIsValid())) { // means user was locked throw new AccountLockedException(); } else if (password.equals(userInfo.getPassword())) { // means correct username/password, login success return true return true; } else { // means wrong password, failure times +1 int failureTimes = userInfo.getFailureTimes(); if (++failureTimes >= Integer.valueOf(maxFailureTimes)) { // touch max failure times, will lock this user template.update(LOCK_USER_SQL, new Object[]{failureTimes, 'N', username}); } else { template.update(FAILURE_TRIGGER_SQL, new Object[]{failureTimes, username}); } // throw wrong password exception throw new BadPasswordAuthenticationException(); } } catch (final IncorrectResultSizeDataAccessException e) { // this means the username was not found. throw new UnknownUsernameAuthenticationException(); } } }
- 3. 编译上述class,把生成的字节码文件放到cas的class path目录下:可以放到WEB-INF\classes目录下,或者打jar放到WEB-INF\lib目录下
- 4. 修改cas部署包下的WEB-INF目录下的deployerConfigContext.xml
- 调整authenticationManager类的描述,在authenticationHandlers list里面添加自定义的这个handler如下:
- 5. 客户化结束,重新部署CAS,3次登录失败之后,帐户自动被锁死。
总结:这个简单的客户化的过程主要依赖spring的jdbc包来实现数据访问,因此需要对spring以及org.springframework.jdbc.core这个包有所了解,用到的核心类是JdbcTemplate,这个类非常强大,提供了各种数据访问的方法,避免直接写JDBC管理连接、执行查询的繁琐,而又省去了配置hibernate或者JPA等第三方持久化框架的麻烦。
通过这个小例子,可以基本了解了Yale CAS的登录模块的实现。虽然Yale CAS真正强大之处在于它的SSO,以及超级复杂的文件配置(不过比起Spring Security的配置来,还是小巫见大巫啦)