Spring JDBC实践之--Yale CAS登录模块的一个典型的客户化

项目使用Yale CAS+Spring Security实现单点登录以及权限验证
需要对Yale CAS的登录模块进行一下改动,如果用户输入帐号密码失败次数超过3次的时候,要把帐号锁定,这样要等管理员解锁以后才可以再次登录这个帐号。
Yale CAS的登录是个典型的密码验证模块,不能提供上述需求,这就需要对CAS的登录部分做些改动
(暂且不考虑这个需求是不是合理,单纯从技术角度来实现这个需求)
具体分析如下:

[list]
[*]1. 一个帐户需要多记录两个属性:密码输错的次数以及帐户是否可登录;
[/list][list]
[*]2. 用户提交登录申请的时候,先检验该帐户是否可登录,如果是,继续进行下面的,如果否,返回登录失败信息;
[/list][list]
[*]3. 校验用户帐户和密码,如果密码正确,返回登录成功,如果密码错误,继续进行下面的;
[/list][list]
[*]4. 该帐户的密码输错次数加1,并比较现在的输错次数是否小于3(可配置),如果是,返回登录失败信息,如果否,继续进行下面的;
[/list][list]
[*]5. 将帐户是否可登录属性设置为否,返回登录失败信息。
[/list]

综合上述分析,实现如下:
[list]
[*]1. 数据库中设计帐户信息时加两个字段failureTimes和isValid来记录错误登录次数和是否被锁定
[/list][list]
[*]2. 重写CAS的密码校验模块,
[/list]

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);
}
}

[list]
[*]3. 编译上述class,把生成的字节码文件放到cas的class path目录下:可以放到WEB-INF\classes目录下,或者打jar放到WEB-INF\lib目录下
[/list][list]
[*]4. 修改cas部署包下的WEB-INF目录下的deployerConfigContext.xml
[*]调整authenticationManager类的描述,在authenticationHandlers list里面添加自定义的这个handler如下:
[/list]

<bean id="authenticationManager"
class="org.jasig.cas.authentication.AuthenticationManagerImpl">

<property name="credentialsToPrincipalResolvers">
<list>
<bean
class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />

<bean
class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
</list>
</property>

<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient" />

<bean class="com.cas.JdbcUsernamePasswordAuthHandlerImpl">
<property name="dataSource" ref="dataSource" /><!--指定数据源-->
<property name="passwordEncoder"><!--设置加密方式,这里要看数据库中存储的密码的加密方式是什么,要配置相应的加密器-->
<bean class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="MD5"/><!-- MD5加密方式-->
</bean>
</property>
<property name="maxFailureTimes" value="3" /><!-- 这里对应JdbcUsernamePasswordAuthHandlerImpl类里的maxFailureTimes属性 -->
</bean>
</list>
</property>
</bean>
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:/CAS" />
<property name="lookupOnStartup" value="true"/>
<property name="resourceRef" value="false" />
</bean>

[list]
[*]5. 客户化结束,重新部署CAS,3次登录失败之后,帐户自动被锁死。
[/list]
[b]总结:[/b]这个简单的客户化的过程主要依赖spring的jdbc包来实现数据访问,因此需要对spring以及org.springframework.jdbc.core这个包有所了解,用到的核心类是JdbcTemplate,这个类非常强大,提供了各种数据访问的方法,避免直接写JDBC管理连接、执行查询的繁琐,而又省去了配置hibernate或者JPA等第三方持久化框架的麻烦。
通过这个小例子,可以基本了解了Yale CAS的登录模块的实现。虽然Yale CAS真正强大之处在于它的SSO,以及超级复杂的文件配置(不过比起Spring Security的配置来,还是小巫见大巫啦)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值