基於數據庫的認證
目錄
1.1 BindModeSearchDatabaseAuthenticationHandler
1.2 QueryDatabaseAuthenticationHandler
1.2.1 PrefixSuffixPrincipalNameTransformer
1.2.2 DefaultPasswordEncoder
1.3 SearchModeSearchDatabaseAuthenticationHandler
Cas Server自身已經爲我們實現了幾種基於JDBC的AuthenticationHandler實現,但它們不包含在Cas Server的核心包裏面,而是包含在cas-server-support-jdbc中,如果我們要使用Cas Server已經實現好的基於JDBC的AuthenticationHandler,我們必須先將cas-server-support-jdbc對應的jar包、相關數據庫的驅動,以及所需要使用的數據源實現等jar包加入Cas Server的類路徑中。如果是基於Maven的war覆蓋機制來修改Cas Server的配置文件,則我們可以在自己的Maven項目的依賴中加入如下項(對應的驅動就沒貼出來了)。
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${cas.version}</version>
<scope>runtime</scope>
</dependency>
Cas Server默認已經實現好的基於JDBC的AuthenticationHandler有三個,它們都繼承自AbstractJdbcUsernamePasswordAuthenticationHandler,而且在認證過程中都需要一個DataSource。下面來對它們做一個簡要的介紹。
1.1 BindModeSearchDatabaseAuthenticationHandler
BindModeSearchDatabaseAuthenticationHandler將試圖以傳入的用戶名和密碼從配置的DataSource中建立一個連接,如果連接成功,則表示認證成功,否則就是認證失敗。以下是BindModeSearchDatabaseAuthenticationHandler源碼的一段主要代碼,通過它我們可以明顯的看清其邏輯:
protected final boolean authenticateUsernamePasswordInternal(
final UsernamePasswordCredentials credentials)
throws AuthenticationException {
final String username = credentials.getUsername();
final String password = credentials.getPassword();
try {
final Connection c = this.getDataSource()
.getConnection(username, password);
DataSourceUtils.releaseConnection(c, this.getDataSource());
returntrue;
} catch (final SQLException e) {
returnfalse;
}
}
當然,這種實現也需要你的DataSource支持getConnection(user,password)才行,否則將返回false。dbcp的BasicDataSource的不支持的,而c3p0的ComboPooledDataSource支持。
以下是一個使用BindModeSearchDatabaseAuthenticationHandler的配置示例:
<bean id="authenticationManager"
class="org.jasig.cas.authentication.AuthenticationManagerImpl">
...
<property name="authenticationHandlers">
<list>
...
<bean class="org.jasig.cas.adaptors.jdbc.BindModeSearchDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
</bean>
...
</list>
</property>
...
</bean>
1.2 QueryDatabaseAuthenticationHandler
使用QueryDatabaseAuthenticationHandler需要我們指定一個SQL,該SQL將接收一個用戶名作爲查詢條件,然後返回對應的密碼。該SQL將被QueryDatabaseAuthenticationHandler用來通過傳入的用戶名查詢對應的密碼,如果存在則將查詢的密碼與查詢出來的密碼進行匹配,匹配結果將作爲認證結果。如果對應的用戶名不存在也將返回false。
以下是QueryDatabaseAuthenticationHandler的一段主要代碼:
protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
final String password = credentials.getPassword();
final String encryptedPassword = this.getPasswordEncoder().encode(
password);
try {
final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
return dbPassword.equals(encryptedPassword);
} catch (final IncorrectResultSizeDataAccessException e) {
// this means the username was not found.
returnfalse;
}
}
上面的邏輯非常明顯。此外,如你所見,QueryDatabaseAuthenticationHandler使用的用戶名會經過PrincipalNameTransformer進行轉換,而密碼會經過PasswordEncoder進行編碼。Cas Server中基於JDBC的AuthenticationHandler實現中使用到的PrincipalNameTransformer默認是不進行任何轉換的NoOpPrincipalNameTransformer,而默認使用的PasswordEncoder也是不會經過任何編碼的PlainTextPasswordEncoder。當然了,cas-server-jdbc-support對它們也有另外兩種支持,即PrefixSuffixPrincipalNameTransformer和DefaultPasswordEncoder。
1.2.1 PrefixSuffixPrincipalNameTransformer
PrefixSuffixPrincipalNameTransformer的作用很明顯,如其名稱所描述的那樣,其在轉換時會將用戶名加上指定的前綴和後綴。所以用戶在使用的時候需要指定prefix和suffix兩個屬性,默認是空。
1.2.2 DefaultPasswordEncoder
DefaultPasswordEncoder底層使用的是標準Java類庫中的MessageDigest進行加密的,其支持MD5、SHA等加密算法。在使用時需要通過構造參數encodingAlgorithm來指定使用的加密算法,可以使用characterEncoding屬性注入來指定獲取字節時使用的編碼,不指定則使用默認編碼。以下是DefaultPasswordEncoder的源碼,其展示了DefaultPasswordEncoder的加密邏輯。
public final class DefaultPasswordEncoder implements PasswordEncoder {
privatestaticfinalchar[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
@NotNull
privatefinal String encodingAlgorithm;
private String characterEncoding;
public DefaultPasswordEncoder(final String encodingAlgorithm) {
this.encodingAlgorithm = encodingAlgorithm;
}
public String encode(final String password) {
if (password == null) {
returnnull;
}
try {
MessageDigest messageDigest = MessageDigest
.getInstance(this.encodingAlgorithm);
if (StringUtils.hasText(this.characterEncoding)) {
messageDigest.update(password.getBytes(this.characterEncoding));
} else {
messageDigest.update(password.getBytes());
}
finalbyte[] digest = messageDigest.digest();
return getFormattedText(digest);
} catch (final NoSuchAlgorithmException e) {
thrownew SecurityException(e);
} catch (final UnsupportedEncodingException e) {
thrownew RuntimeException(e);
}
}
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes the raw bytes from the digest.
* @return the formatted bytes.
*/
private String getFormattedText(byte[] bytes) {
final StringBuilder buf = new StringBuilder(bytes.length * 2);
for (int j = 0; j < bytes.length; j++) {
buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
}
return buf.toString();
}
publicfinalvoid setCharacterEncoding(final String characterEncoding) {
this.characterEncoding = characterEncoding;
}
}
如果在認證時需要使用DefaultPasswordEncoder,則需要確保數據庫中保存的密碼的加密方式和DefaultPasswordEncoder的加密算法及邏輯是一致的。如果這些都不能滿足你的需求,則用戶可以實現自己的PrincipalNameTransformer和PasswordEncoder。
以下是一個配置使用QueryDatabaseAuthenticationHandler進行認證,且使用DefaultPasswordEncoder對密碼進行MD5加密的示例:
<bean id="authenticationManager"
class="org.jasig.cas.authentication.AuthenticationManagerImpl">
...
<property name="authenticationHandlers">
<list>
...
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="sql" value="select password from t_user where username = ?"/>
</bean>
...
</list>
</property>
...
</bean>
<bean id="passwordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder">
<constructor-arg value="MD5"/><!-- 加密算法 -->
</bean>
1.3 SearchModeSearchDatabaseAuthenticationHandler
SearchModeSearchDatabaseAuthenticationHandler的主要邏輯是將傳入的用戶名和密碼作爲條件從指定的表中進行查詢,如果對應記錄存在則表示認證通過。使用該AuthenticationHandler時需要我們指定查詢時使用的表名(tableUsers)、用戶名對應的字段名(fieldUser)和密碼對應的字段名(fieldPassword)。此外,還可以選擇性的使用PrincipalNameTransformer和PasswordEncoder。以下是SearchModeSearchDatabaseAuthenticationHandler源碼中的一段主要代碼:
private static final String SQL_PREFIX = "Select count('x') from ";
@NotNull
private String fieldUser;
@NotNull
private String fieldPassword;
@NotNull
private String tableUsers;
private String sql;
protectedfinalboolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
final String transformedUsername = getPrincipalNameTransformer().transform(credentials.getUsername());
final String encyptedPassword = getPasswordEncoder().encode(credentials.getPassword());
finalint count = getJdbcTemplate().queryForInt(this.sql,
transformedUsername, encyptedPassword);
return count > 0;
}
publicvoid afterPropertiesSet() throws Exception {
this.sql = SQL_PREFIX + this.tableUsers + " Where " + this.fieldUser
+ " = ? And " + this.fieldPassword + " = ?";
}
以下是一個使用SearchModeSearchDatabaseAuthenticationHandler的配置示例:
<bean id="authenticationManager"
class="org.jasig.cas.authentication.AuthenticationManagerImpl">
...
<property name="authenticationHandlers">
<list>
...
<bean class="org.jasig.cas.adaptors.jdbc.SearchModeSearchDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"/>
<property name="passwordEncoder" ref="passwordEncoder"/>
<property name="tableUsers" value="t_user"/><!-- 指定從哪個用戶表查詢用戶信息 -->
<property name="fieldUser" value="username"/><!-- 指定用戶名在用戶表對應的字段名 -->
<property name="fieldPassword" value="password"/><!-- 指定密碼在用戶表對應的字段名 -->
</bean>
...
</list>
</property>
...
</bean>
至此,cas-server-support-jdbc中支持JDBC的三個AuthenticationHandler就講完了。如果用戶覺得它們都不能滿足你的要求,則還可以選擇使用自己實現的AuthenticationHandler。至於其它認證方式,請參考官方文檔。
(注:本文是基於cas 3.5.2所寫)
(注:原創文章,轉載請註明出處。原文地址:http://haohaoxuexi.iteye.com/blog/2142616)