基於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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章