代码改变世界

CAS扩展——自定义查询数据库验证Handler

2012-11-13 11:04 飘扬的红领巾 阅读(...) 评论(...) 编辑 收藏

问题由来

    当我们使用CAS来搭建我们的单点登录系统时,由于CAS默认的登录认证是简单的用户名和密码相同即可通过认证,所以我们使用时经常需要通过查询数据库来认证用户名和密码。这便需要扩展CAS验证的Handler。

解决方案

    首先找到CAS中很重要的一个配置文件,deployerConfigContext.xml,在X:\tomcat6\webapps\cas-server\WEB-INF下。找到

   1:  <bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />

    这段是CAS默认的登录方式,即用户名和密码相同即可通过认证。我们现在需要改造它使之可以通过数据库查询的用户名和密码进行验证。

    把上面的代码替换成:

   1:  <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">  
   2:      <property name="sql" value="select password from ucenter_user where username=? " />  
   3:      <property name="dataSource" ref="dataSource" />  
   4:  </bean>

    CAS认证的逻辑是,先通过sql获取该用户名的密码,然后用该密码和用户名输入的密码进行比对,如果相同则通过认证。

    dataSource是数据库配置:

   1:  <bean id="dataSource"
   2:          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   3:          <property name="driverClassName">
   4:              <value>oracle.jdbc.driver.OracleDriver</value>
   5:          </property>
   6:          <property name="url">
   7:              <value>jdbc:oracle:thin:@192.168.2.233:1521:splexsrc</value>
   8:          </property>
   9:          <property name="username">
  10:              <value>username</value>
  11:          </property>
  12:          <property name="password">
  13:              <value>passport</value>
  14:          </property>
  15:      </bean>

    这样做已基本满足大部分业务的需求,但如果遇到一些特殊的需求,如要求登录支持用户名、邮箱地址、手机号码。你该怎么办呢?

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">  
    <property name="sql" value="select password from ucenter_user where username=? or email=? or mobile=?" />  
    <property name="dataSource" ref="dataSource" />  
</bean> 

    是这样吗?那我们看看QueryDatabaseAuthenticationHandler.java的源码:

   1:  public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
   2:   
   3:      @NotNull
   4:      private String sql;
   5:   
   6:      protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
   7:          final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
   8:          final String password = credentials.getPassword();
   9:          final String encryptedPassword = this.getPasswordEncoder().encode(
  10:              password);
  11:          
  12:          try {
  13:              final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
  14:              return dbPassword.equals(encryptedPassword);
  15:          } catch (final IncorrectResultSizeDataAccessException e) {
  16:              // this means the username was not found.
  17:              return false;
  18:          }
  19:      }
  20:   
  21:      /**
  22:       * @param sql The sql to set.
  23:       */
  24:      public void setSql(final String sql) {
  25:          this.sql = sql;
  26:      }
  27:  }

    注意看line 13,QueryDatabaseAuthenticationHandler.java只支持一个参数,也就是说我们前面的写法是错误的,那么找到了原因,解决就很简单了。

    自定义一个验证Handler,同样继承AbstractJdbcUsernamePasswordAuthenticationHandler.java.

   1:  public class UCQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
   2:   
   3:      @NotNull
   4:      private String sql;
   5:   
   6:      protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
   7:          final String id = getPrincipalNameTransformer().transform(credentials.getUsername());
   8:          final String password = credentials.getPassword();
   9:          final String encryptedPassword = this.getPasswordEncoder().encode(
  10:              password);
  11:          
  12:          try {
  13:              final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, id,id,id);
  14:              return dbPassword.equals(encryptedPassword);
  15:          } catch (final IncorrectResultSizeDataAccessException e) {
  16:              // this means the username was not found.
  17:              return false;
  18:          }
  19:      }
  20:   
  21:      /**
  22:       * @param sql The sql to set.
  23:       */
  24:      public void setSql(final String sql) {
  25:          this.sql = sql;
  26:      }
  27:  }

    注意line13,因为传入的sql=select password from ucenter_user where username=? or email=? or mobile=?。所以这里在查询时需要配置三个参数,均是用户名登陆填写的用户名(邮箱、手机号)。这样就满足了我们的需求,支持用户名、邮箱、手机号码登陆。