通过配置方式实现数据库查询认证,的确简单但是不够灵活。但是如果登录验证逻辑稍微复杂些,可能通过配置方式就不能满足需求了,比如:当用户登录时,需要判断该用户是否绑定了邮箱,如果未绑定,拒绝登录并给出提示信息。
遇到类似的情况,就需要使用自定义登录来完成,并且给出的提示信息也是自定义的。
自定义登录认证
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>