Spring JDBC实践之--Yale CAS登录模块的一个典型的客户化
项目使用Yale CAS+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(); } }}
其中AccountLockedException是自定义的一个异常类,代码如下:
package com.cas;import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException;/** * @author * @Date */public class AccountLockedException extends BadCredentialsAuthenticationException { /** Static instance of AccountLockedException. */ public static final AccountLockedException ERROR = new AccountLockedException(); /** Unique ID for serializing. */ private static final long serialVersionUID = 6831383559080393480L; /** The code description of this exception. *//**这个自定义的异常码用来显示登录失败信息,对应的message配置在cas部署包的classes目录下的messages.properties文件里,支持国际化*/ private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.maxtrycount"; /** * Default constructor that does not allow the chaining of exceptions and uses the default code as the error code * for this exception. */ public AccountLockedException() { super(CODE); } /** * Constructor that allows for the chaining of exceptions. Defaults to the default code provided for this exception. * * @param throwable * the chained exception. */ public AccountLockedException(final Throwable throwable) { super(CODE, throwable); } /** * Constructor that allows for providing a custom error code for this class. Error codes are often used to resolve * exceptions into messages. Providing a custom error code allows the use of a different message. * * @param code * the custom code to use with this exception. */ public AccountLockedException(final String code) { super(code); } /** * Constructor that allows for chaining of exceptions and a custom error code. * * @param code * the custom error code to use in message resolving. * @param throwable * the chained exception. */ public AccountLockedException(final String code, final Throwable throwable) { super(code, throwable); }}
3. 编译上述class,把生成的字节码文件放到cas的class path目录下:可以放到WEB-INF\classes目录下,或者打jar放到WEB-INF\lib目录下4. 修改cas部署包下的WEB-INF目录下的deployerConfigContext.xml调整authenticationManager类的描述,在authenticationHandlers list里面添加自定义的这个handler如下:
<bean id="authenticationManager"/><bean/></list></property><property name="authenticationHandlers"><list><bean /><bean ref="dataSource" /><!--指定数据源--><property name="passwordEncoder"><!--设置加密方式,这里要看数据库中存储的密码的加密方式是什么,要配置相应的加密器--><bean value="3" /><!-- 这里对应JdbcUsernamePasswordAuthHandlerImpl类里的maxFailureTimes属性 --></bean></list></property></bean><bean id="dataSource"value="java:/CAS" /><property name="lookupOnStartup" value="true"/><property name="resourceRef" value="false" /></bean>
5. 客户化结束,重新部署CAS,3次登录失败之后,帐户自动被锁死。
总结:这个简单的客户化的过程主要依赖spring的jdbc包来实现数据访问,因此需要对spring以及org.springframework.jdbc.core这个包有所了解,用到的核心类是JdbcTemplate,这个类非常强大,提供了各种数据访问的方法,避免直接写JDBC管理连接、执行查询的繁琐,而又省去了配置hibernate或者JPA等第三方持久化框架的麻烦。
通过这个小例子,可以基本了解了Yale CAS的登录模块的实现。虽然Yale CAS真正强大之处在于它的SSO,以及超级复杂的文件配置(不过比起Spring Security的配置来,还是小巫见大巫啦) 1 楼 vincent_com 2011-04-18 [color=red][/color]