基于CAS实现单点登录(SSO):自定义登录验证方法

通过配置方式实现数据库查询认证,的确简单但是不够灵活。但是如果登录验证逻辑稍微复杂些,可能通过配置方式就不能满足需求了,比如:当用户登录时,需要判断该用户是否绑定了邮箱,如果未绑定,拒绝登录并给出提示信息。

遇到类似的情况,就需要使用自定义登录来完成,并且给出的提示信息也是自定义的。

 

自定义登录认证

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>

发布了130 篇原创文章 · 获赞 433 · 访问量 45万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章