通過配置方式實現數據庫查詢認證,的確簡單但是不夠靈活。但是如果登錄驗證邏輯稍微複雜些,可能通過配置方式就不能滿足需求了,比如:當用戶登錄時,需要判斷該用戶是否綁定了郵箱,如果未綁定,拒絕登錄並給出提示信息。
遇到類似的情況,就需要使用自定義登錄來完成,並且給出的提示信息也是自定義的。
自定義登錄認證
CAS內置了一些AuthenticationHandler實現類,如下圖所示,在cas-server-support-jdbc包中提供了基於jdbc的認證類。
如果需要實現自定義登錄,只需要實現org.jasig.cas.authentication.handler.AuthenticationHandler接口即可,
當然也可以利用已有的實現,比如創建一個繼承自
org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler的類,
實現方法可以參考org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler類:
packageorg.jasig.cas.adaptors.jdbc;
importorg.jasig.cas.authentication.handler.AuthenticationException;
importorg.jasig.cas.authentication.principal.UsernamePasswordCredentials;
importorg.springframework.dao.IncorrectResultSizeDataAccessException;
importjavax.validation.constraints.NotNull;
public final class QueryDatabaseAuthenticationHandlerextends
AbstractJdbcUsernamePasswordAuthenticationHandler {
@NotNull
private String sql;
protected final booleanauthenticateUsernamePasswordInternal(final UsernamePasswordCredentialscredentials) throws AuthenticationException {
final String username =getPrincipalNameTransformer().transform(credentials.getUsername());
final String password =credentials.getPassword();
final StringencryptedPassword = this.getPasswordEncoder().encode(
password);
try {
final String dbPassword =getJdbcTemplate().queryForObject(
this.sql,String.class, username);
returndbPassword.equals(encryptedPassword);
} catch (finalIncorrectResultSizeDataAccessException e) {
// this means theusername was not found.
return false;
}
}
/**
* @param sql The sql toset.
*/
public void setSql(final Stringsql) {
this.sql = sql;
}
}
修改authenticateUsernamePasswordInternal方法中的代碼爲自己的認證邏輯即可。
注意:不同版本的handler實現上稍有差別,請參考對應版本的hanlder,本文以3.4爲例。
自定義登錄錯誤提示消息
在自定義的AuthenticationHandler類的驗證方法中拋出繼承自AuthenticationException的異常,
登錄頁面(默認爲WEB-INF/view/jsp/default/ui/casLoginView.jsp)中的SpringSecurity驗證表單將會自動輸出
該異常對應的錯誤消息。
CAS AuthenticationException結構如下圖,CAS已經內置了一些異常,比如用戶名密碼錯誤、未知的用戶名錯誤等。
假設這樣一個需求:用戶註冊時需要驗證郵箱才能登錄,如果未驗證郵箱,則提示用戶還未驗證郵箱,拒絕登錄。
爲實現未驗證郵箱後提示用戶的需求,定義一個繼承自AuthenticationException的
類:UnRegisterEmailAuthenticationException,代碼示例如下:
package test;
importorg.jasig.cas.authentication.handler.BadUsernameOrPasswordAuthenticationException;
public classUnRegisterEmailAuthenticationException extendsBadUsernameOrPasswordAuthenticationException {
/** Static instance ofUnknownUsernameAuthenticationException. */
public static finalUnRegisterEmailAuthenticationException ERROR = newUnRegisterEmailAuthenticationException();
/** Unique ID for serializing.*/
private static final longserialVersionUID = 3977861752513837361L;
/** The code description of thisexception. */
private static final String CODE= "error.authentication.credentials.bad.unregister.email";
/**
* Default constructor that doesnot allow the chaining of exceptions and
* uses the default code as theerror code for this exception.
*/
public UnRegisterEmailAuthenticationException(){
super(CODE);
}
/**
* Constructor that allows forthe chaining of exceptions. Defaults to the
* default code provided for thisexception.
*
* @param throwable the chainedexception.
*/
publicUnRegisterEmailAuthenticationException(final Throwable throwable) {
super(CODE,throwable);
}
/**
* Constructor that allows forproviding a custom error code for this class.
* Error codes are often used toresolve exceptions into messages. Providing
* a custom error code allows theuse of a different message.
*
* @param code the custom code touse with this exception.
*/
publicUnRegisterEmailAuthenticationException(final String code) {
super(code);
}
/**
* Constructor that allows forchaining of exceptions and a custom error
* code.
*
* @param code the custom errorcode to use in message resolving.
* @param throwable the chainedexception.
*/
publicUnRegisterEmailAuthenticationException(final String code,
final Throwable throwable){
super(code,throwable);
}
}
請注意代碼中的CODE私有屬性,該屬性定義了一個本地化資源文件中的鍵,通過該鍵獲取本地化資源中對
應語言的文字,這裏只實現中文錯誤消息提示,修改WEB-INF/classes/messages_zh_CN.properties文件,添加
CODE定義的鍵值對,如下示例:
error.authentication.credentials.bad.unregister.email=\u4f60\u8fd8\u672a\u9a8c\u8bc1\u90ae\u7bb1\uff0c\u8bf
7\u5148\u9a8c\u8bc1\u90ae\u7bb1\u540e\u518d\u767b\u5f55
後面的文字是使用jdk自帶的native2ascii編碼工具:native2ascii、native2ascii-reverse,轉換成utf-8格式。
接下來只需要在自定義的AuthenticationHandler類的驗證方法中,驗證失敗的地方拋出異常即可。
自定義AuthenticationHandler示例代碼如下:
package cn.test.web;
importjavax.validation.constraints.NotNull;
importorg.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
importorg.jasig.cas.authentication.handler.AuthenticationException;
importorg.jasig.cas.authentication.principal.UsernamePasswordCredentials;
importorg.springframework.dao.IncorrectResultSizeDataAccessException;
public classCustomQueryDatabaseAuthenticationHandler extendsAbstractJdbcUsernamePasswordAuthenticationHandler {
@NotNull
private String sql;
@Override
protected booleanauthenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials)throws AuthenticationException {
final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
final String password =credentials.getPassword();
final String encryptedPassword= this.getPasswordEncoder().encode(password);
try {
// 查看郵箱是否已經驗證。
Boolean isEmailValid=EmailValidation.Valid();
if(!isEmailValid){
throw newUnRegisterEmailAuthenticationException();
}
//其它驗證
……
} catch (finalIncorrectResultSizeDataAccessException e) {
// this means theusername was not found.
return false;
}
}
public void setSql(final Stringsql) {
this.sql = sql;
}
}
配置使自定義登錄認證生效
最後需要修改AuthenticationManager bean的配置(一般爲修改WEB-INF/spring-configuration/applicationContext.xml文
件),加入自定義的AuthenticationHandler,配置示例如下:
<beanid="authenticationManager"class="org.jasig.cas.authentication.AuthenticationManagerImpl">
<propertyname="credentialsToPrincipalResolvers">
<list>
<beanclass="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver">
<propertyname="attributeRepository" ref="attributeRepository"/>
</bean>
<beanclass="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver"/>
</list>
</property>
<propertyname="authenticationHandlers">
<list>
<beanclass="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"p:requireSecure="false" />
<beanclass="cn.test.web.CustomQueryDatabaseAuthenticationHandler">
<propertyname="sql" value="select password from t_user whereuser_name=?" />
<propertyname="dataSource" ref="dataSource" />
<property name="passwordEncoder"ref="passwordEncoder"></property>
</bean>
</list>
</property>
</bean>