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