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"
	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>


  • 5. 客户化结束,重新部署CAS,3次登录失败之后,帐户自动被锁死。

总结:这个简单的客户化的过程主要依赖spring的jdbc包来实现数据访问,因此需要对spring以及org.springframework.jdbc.core这个包有所了解,用到的核心类是JdbcTemplate,这个类非常强大,提供了各种数据访问的方法,避免直接写JDBC管理连接、执行查询的繁琐,而又省去了配置hibernate或者JPA等第三方持久化框架的麻烦。 
通过这个小例子,可以基本了解了Yale CAS的登录模块的实现。虽然Yale CAS真正强大之处在于它的SSO,以及超级复杂的文件配置(不过比起Spring Security的配置来,还是小巫见大巫啦)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值