Tomcat的安全性


Web應用程序的一些內容是受限的,只有授權的用戶在提供了正確的用戶名和密碼后才能查看他們,servlet技術支持通過配置部署 描述器(web.xml)來對這些內容進行訪問控制,那么web容器是怎么樣支持安全限制功能的呢?servlet容器是通過一個名為“驗證器閥”來支持安全限制的,當servlet容器啟動時,驗證器閥 會被添加到Context容器的管道中,在調用Wrapper的基礎閥之前,會先調用驗證器閥,對當前用戶進行身份驗證,如果用戶輸入了正確的用戶和密碼,則驗證器閥會調用后續的閥,繼續顯示請求的servlet,如果用戶未能通過身份驗證,額驗證器閥會直接返回,而不會調用后面的閥,所以身份驗證失敗的話,用戶就無法查看想要訪問的servlet資源了,

那驗證器閥具體是怎么工作的呢?驗證器閥 會調用Context容器的領域對象的authenticate()方法,傳入用戶輸入的用戶名和密碼,來對用戶身份進行驗證,領域對象可以訪問當前Context容器中有效用戶的用戶名和密碼的集合,

  • 具體先介紹一下領域對象,領域對象是用來對用戶身份進行驗證的組件,它會用戶輸入的用戶和密碼進行有效性的判斷,領域對象通常都會與一個Context容器相互關聯,而一個Context容器也只能與一個領域對相關聯,可以調用Context容器的setRealm方法來將領域對象與Context容器相關聯,
  • 實際上領域對象保存了所有有效用戶的用戶名和密碼集合,或者它會訪問存儲這些數據的存儲器,這些數據的具體存儲依賴於領域對象的具體實現,在Tomcat中,有效用戶信息默認存儲在tomcat-user.xml文件中,但是可以使用其他類型的領域對象來針對其他類型性的資源驗證用戶身份,例如查詢一個關系型數據庫,在Catalina中,領域對象時org.apache.catalina.Realm接口的實例,該接口中有四個用來對用戶進行身份驗證的的重載方法,Realm接口詳情如下:

  •  

    package org.apache.catalina;
    
    import java.beans.PropertyChangeListener;
    import java.security.Principal;
    import java.security.cert.X509Certificate;
    
    /**
     * 
     * <p>
     * Title:Realm.java
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:僅學習時使用
     * </p>
     * <dl>
     * <br>
     * <p>
     * 領域的定位其實就是代表了一個Context容器, 類功能描述: 領域對象是用來對用戶進行身份驗證的組件,它會對用戶輸入的用戶名和密碼對 進行
     * 有效性的判斷,領域對象通常都是與一個 {@link Context}
     * 容器相關聯,而一個{@code Context}容器也只能與一個領域對象,可以調用{@code Context}對象的
     * {@code setRealm() }方法來將領域對象與該{@code Context}容器對象相關聯, 領域對象是怎么實現驗證用戶身份的呢?實際上
     * 它保存了所有有效用戶的用戶名合和密碼對,或者它會訪問存儲這些數據的存儲器,這些數據的具體存儲依賴於領域對象的 具體實現,
     * 在{@code Tomcat}中,有效的用戶信息默認存儲在{@code tomcat-user.xml}文件中,但是可以使用其他的領域對象的
     * 實現來針對其他資源驗證用戶身份,例如查詢一個關系型數據庫。 在{@code Tomcat}中,領域對象是 該接口的實例,
     * </p>
     * </br>
     * </dl>
     * 
     * @author 陳東
     * @date 2018年11月21日 下午9:19:11
     * @version 1.0
     */
    public interface Realm {
    
        // ------------------------------------------------------------- Properties
    
        /**
         * 
         * 
         * <p>
         * Title: getContainer
         * </p>
         * 
         * @date 2018年11月21日 下午9:28:54
         * 
         *       <p>
         *       功能描述: 返回與這個領域相關聯的{@link Context}容器實例
         *       </p>
         * 
         * @return
         */
        public Container getContainer();
    
        /**
         * 
         * 
         * <p>
         * Title: setContainer
         * </p>
         * 
         * @date 2018年11月21日 下午9:29:42
         * 
         *       <p>
         *       功能描述:設置與該領域關聯的{@link Context}容器
         *       </p>
         * 
         * @param container
         *            要與該領域關聯的{@link Context}容器
         */
        public void setContainer(Container container);
    
        /**
         * Return descriptive information about this Realm implementation and the
         * corresponding version number, in the format
         * <code>&lt;description&gt;/&lt;version&gt;</code>.
         */
        public String getInfo();
    
        // --------------------------------------------------------- Public Methods
    
        /**
         * Add a property change listener to this component.
         *
         * @param listener
         *            The listener to add
         */
        public void addPropertyChangeListener(PropertyChangeListener listener);
    
        /**
         * 返回與指定用戶名和憑據關聯的主體對象,如果存在的話;否則返回<code> null </code>。
         *
         * @param username
         *            要查找的主體用戶名
         * @param credentials
         *            在驗證用戶名時使用的密碼或其他憑據
         */
        public Principal authenticate(String username, String credentials);
    
        /**
         * 返回與指定用戶名和憑據關聯的主體對象,如果存在的話;否則返回<code> null </code>。
         *
         * @param username
         *            Username of the Principal to look up
         * @param credentials
         *            Password or other credentials to use in authenticating this
         *            username
         */
        public Principal authenticate(String username, byte[] credentials);
    
        /**
         * Return the Principal associated with the specified username, which
         * matches the digest calculated using the given parameters using the method
         * described in RFC 2069; otherwise return <code>null</code>.
         *
         * @param username
         *            Username of the Principal to look up
         * @param digest
         *            Digest which has been submitted by the client
         * @param nonce
         *            Unique (or supposedly unique) token which has been used for
         *            this request
         * @param realm
         *            Realm name
         * @param md5a2
         *            Second MD5 digest used to calculate the digest : MD5(Method +
         *            ":" + uri)
         */
        public Principal authenticate(String username, String digest, String nonce, String nc, String cnonce, String qop,
                String realm, String md5a2);
    
        /**
         * Return the Principal associated with the specified chain of X509 client
         * certificates. If there is none, return <code>null</code>.
         *
         * @param certs
         *            Array of client certificates, with the first one in the array
         *            being the certificate of the client itself.
         */
        public Principal authenticate(X509Certificate certs[]);
    
        /**
         * 如果在此領域指定的主體具有指定的安全角色,則返回<code>true</code>;否則返回<code>false</code>。
         *
         * @param principal
         *            Principal for whom the role is to be checked
         * @param role
         *            Security role to be checked
         */
        public boolean hasRole(Principal principal, String role);
    
        /**
         * Remove a property change listener from this component.
         *
         * @param listener
         *            The listener to remove
         */
        public void removePropertyChangeListener(PropertyChangeListener listener);
    
    }

     

     

     

     通常都會使用第一個重載的方法

  • /**
         * 返回與指定用戶名和憑據關聯的主體對象,如果存在的話;否則返回<code> null </code>。
         *
         * @param username
         *            要查找的主體用戶名
         * @param credentials
         *            在驗證用戶名時使用的密碼或其他憑據
         */
        public Principal authenticate(String username, String credentials);

     

  •   在Catalina中,驗證器閥會調用附加到其Context容器中領域對象的authenticate方法來驗證用戶身份, 

  • GenericPrincipal類,

  • 主體對象是Java.security.Principal接口的實例,該接口在Catalina中的實現是 org.apache.realm.GenericPrincipal類,該類必須始終與一個領域對象相關聯,其內容如下
  •   1 package org.apache.catalina.realm;
      2 
      3 import java.security.Principal;
      4 import java.util.Arrays;
      5 import java.util.List;
      6 import org.apache.catalina.Realm;
      7 
      8 /**
      9  * 
     10  * <p>
     11  * Title:GenericPrincipal.java
     12  * </p>
     13  * <p>
     14  * Copyright:ChenDong 2018
     15  * </p>
     16  * <p>
     17  * Company:僅學習時使用
     18  * </p>
     19  * <p>
     20  * 類功能描述:繼承自{@link Principal}接口的 主體對象的的實例,主體對象必須始終與一個領域對象相關聯,且只能與一個 領域對象實例相關聯
     21  * </p>
     22  * 
     23  * @author 陳東
     24  * @date 2018年11月21日 下午9:35:06
     25  * @version 1.0
     26  */
     27 public class GenericPrincipal implements Principal {
     28 
     29     // ----------------------------------------------------------- Constructors
     30 
     31     /**
     32      * 為指定的用戶名和密碼構造一個新的主體,並且與指定的領域相關聯。
     33      *
     34      * @param realm
     35      *            擁有此主體的領域
     36      * @param name
     37      *            由該主體表示的用戶的用戶名
     38      * @param password
     39      *            用於驗證此用戶的憑據
     40      */
     41     public GenericPrincipal(Realm realm, String name, String password) {
     42 
     43         this(realm, name, password, null);
     44 
     45     }
     46 
     47     /**
     48      * 為指定的用戶名和密碼構造一個新的主體,並且與指定的領域相關聯。並傳入一個角色列表 字符串數組
     49      * 
     50      *
     51      * @param realm
     52      *            擁有這個主體的領域
     53      * @param name
     54      *            由該主體表示的用戶的用戶名
     55      * @param password
     56      *            用於驗證此用戶的憑據
     57      * @param roles
     58      *            這個用戶擁有的角色(必須是字符串)
     59      */
     60     public GenericPrincipal(Realm realm, String name, String password, List roles) {
     61 
     62         super();
     63         this.realm = realm;
     64         this.name = name;
     65         this.password = password;
     66         if (roles != null) {
     67             this.roles = new String[roles.size()];
     68             this.roles = (String[]) roles.toArray(this.roles);
     69             if (this.roles.length > 0)
     70                 Arrays.sort(this.roles);
     71         }
     72 
     73     }
     74 
     75     // ------------------------------------------------------------- Properties
     76 
     77     /**
     78      * 由該主體表示的用戶的用戶名。
     79      */
     80     protected String name = null;
     81 
     82     /**
     83      * 返回由該主體代表用戶的用戶名
     84      */
     85     public String getName() {
     86         return (this.name);
     87     }
     88 
     89     /**
     90      * 由該主體表示的用戶的身份驗證憑據。
     91      */
     92     protected String password = null;
     93 
     94     /**
     95      * 
     96      * 
     97      * <p>
     98      * Title: getPassword
     99      * </p>
    100      * 
    101      * @date 2018年11月21日 下午9:47:39
    102      * 
    103      *       <p>
    104      *       功能描述: 返回這個主體代表用戶的身份驗證憑據
    105      *       </p>
    106      * 
    107      * @return
    108      */
    109     public String getPassword() {
    110         return (this.password);
    111     }
    112 
    113     /**
    114      * 與此主體相關聯的領域。
    115      */
    116     protected Realm realm = null;
    117 
    118     public Realm getRealm() {
    119         return (this.realm);
    120     }
    121 
    122     /**
    123      * 與此用戶關聯的一組角色。
    124      */
    125     protected String roles[] = new String[0];
    126 
    127     /**
    128      * 
    129      * 
    130      * <p>
    131      * Title: getRoles
    132      * </p>
    133      * 
    134      * @date 2018年11月21日 下午9:47:08
    135      * 
    136      *       <p>
    137      *       功能描述: 返回這個主體代表用戶擁有的角色列表
    138      *       </p>
    139      * 
    140      * @return
    141      */
    142     public String[] getRoles() {
    143         return (this.roles);
    144     }
    145 
    146     // --------------------------------------------------------- Public Methods
    147 
    148     /**
    149      * 這個主體所代表的用戶是否擁有指定的角色?
    150      *
    151      * @param role
    152      *            待檢查角色
    153      */
    154     public boolean hasRole(String role) {
    155         if ("*".equals(role))
    156             return true;
    157 
    158         if (role == null)
    159             return (false);
    160         return (Arrays.binarySearch(roles, role) >= 0);
    161 
    162     }
    163 
    164     /**
    165      * Return a String representation of this object, which exposes only
    166      * information that should be public.
    167      */
    168     public String toString() {
    169 
    170         StringBuffer sb = new StringBuffer("GenericPrincipal[");
    171         sb.append(this.name);
    172         sb.append("]");
    173         return (sb.toString());
    174 
    175     }
    176 
    177 }

    該實例必須擁有一個用戶名和密碼對,此外該用戶名和密碼所對應的角色集合列表是可選的,然后可以調用其hasRole方法,並傳入一個字符串形式的角色名稱來檢查該主體對象是否擁有指定的角色。

  • LoginConfig類:

  • 登錄配置 是final型的org.apache.catalina.deploy.LoginConfig 的實例,其中包含一個領域對象的名字,LoginConfig實例封裝了領域對象和所要使用的身份驗證方法,可以調用LogConfig實例的getRealmName方法來獲取領域對象的名字,並調用其

    getAuthMethod
    方法來返回所使用的身份驗證方法的名字,獲取的身份驗證方法的名字必須是以下之一 “BASIC”、“DIGEST”、“FORM”、“CLIENT-CERT”,如果使用的是基於表單的身份驗證方法,LoginConfig實例還需要在LoginPage屬性和errorPage屬性分別存儲字符串形式的登錄頁面和錯誤頁面的URL,在實際部署中,Tomcat在啟動時需要讀取web.xml文件的內容,如果web.xml文件包含login-config元素的配置,則Tomcat會創建一個LoginConfig實例對象,並設置其相應的屬性,驗證器閥會調用LoginConfig實例的getRealName方法來獲取領域對象名,並將該領域對象發送到瀏覽器,顯示在登錄對話框中,如果getRealmName返回的值是null,則會將服務器名和相應端口發送給瀏覽器,下面是其實現
  • package org.apache.catalina.deploy;
    
    import org.apache.catalina.util.RequestUtil;
    
    /**
     * 
     * <p>
     * Title:LoginConfig.java
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:僅學習時使用
     * </p>
     * <p>
     * 類功能描述: {@link LoginConfig} 類
     * 是登錄配置的實例,其中包含一個領域對象的名字,{@link LoginConfig}類封裝了領域對象名和所要使用的身份驗證方法,
     * 可以調用{@link LoginConfig}實例的
     * {@code getRealmName}方法來獲取領域對象的名字,並調用其{@code getAuthName}方法來獲取使用的身份驗證方法的名字,
     * 獲取的身份驗證方法的名字必須是以下名字之一:{@code BASIC}、{@code DIGEST}、{@code FORM}、
     * {@code CLIENT-CERT}。如果使用的是基於表單的身份驗證方法,{@link LoginConfig}實例還需要在
     * {@code LoginPage}屬性 和{@code errorPage}屬性中分別 存儲字符串形式的登錄頁面和錯誤頁面的URL,
     * 在實際部署中,{@code Tomcat}在啟動時需要讀取{@code web.xml}文件的內容,如果{@code web.xml}文件包含
     * {@code log-config}元素的配置,
     * 則{@code Tomcat}會創建一個{@link LoginConfig}對象,並設置其相應的屬性,驗證器閥會調用
     * {@link LoginConfig}實例的{@code getRealmName}來獲取領域對象名,並將該領域對象名發送到瀏覽器,
     * </p>
     * 
     * <p>
     *
     * 
     * </p>
     * 
     * @author 陳東
     * @date 2018年11月21日 下午9:55:15
     * @version 1.0
     */
    public final class LoginConfig {
    
        // ----------------------------------------------------------- Constructors
    
        /**
         * 構造具有默認屬性的新{@link LoginConfig}
         */
        public LoginConfig() {
    
            super();
    
        }
    
        /**
         * 構造一個具有指定屬性的新{@link LoginConfig}
         *
         * @param authMethod
         *            認證方法名字
         * @param realmName
         *            領域名字
         * @param loginPage
         *            登錄頁面URL
         * @param errorPage
         *            錯誤頁面URL
         */
        public LoginConfig(String authMethod, String realmName, String loginPage, String errorPage) {
    
            super();
            setAuthMethod(authMethod);
            setRealmName(realmName);
            setLoginPage(loginPage);
            setErrorPage(errorPage);
    
        }
    
        // ------------------------------------------------------------- Properties
    
        /**
         * 用於應用程序登錄的身份驗證方法。 必須是 {@code BASIC}、{@code DIGEST}、{@code FORM}、
         * {@code CLIENT-CERT}
         *
         */
        private String authMethod = null;
    
        /**
         * 
         * 
         * <p>
         * Title: getAuthMethod
         * </p>
         * 
         * @date 2018年11月21日 下午10:09:31
         * 
         *       <p>
         *       功能描述: 返回這個登錄配置用於應用程序登錄的身份驗證方法
         *       </p>
         * 
         * @return
         */
        public String getAuthMethod() {
            return (this.authMethod);
        }
    
        /**
         * 
         * 
         * <p>
         * Title: setAuthMethod
         * </p>
         * 
         * @date 2018年11月21日 下午10:10:21
         * 
         *       <p>
         *       功能描述: 設置登錄配置 中用於應用程序登錄的身份驗證方法
         *       </p>
         * 
         * @param authMethod
         *            身份驗證方法
         */
        public void setAuthMethod(String authMethod) {
            this.authMethod = authMethod;
        }
    
        /**
         * 表格登錄錯誤頁面的URI.
         */
        private String errorPage = null;
    
        /**
         * 
         * 
         * <p>
         * Title: getErrorPage
         * </p>
         * 
         * @date 2018年11月21日 下午10:12:03
         * 
         *       <p>
         *       功能描述: 獲取該登錄配置 在表單形式 登錄錯誤時 錯誤頁面的URL
         *       </p>
         * 
         * @return
         */
        public String getErrorPage() {
            return (this.errorPage);
        }
    
        /**
         * 
         * 
         * <p>
         * Title: setErrorPage
         * </p>
         * 
         * @date 2018年11月21日 下午10:12:49
         * 
         *       <p>
         *       功能描述: 設置該登錄配置 在表單形式登錄錯誤時 錯誤頁面的URL
         *       </p>
         * 
         * @param errorPage
         */
        public void setErrorPage(String errorPage) {
            // if ((errorPage == null) || !errorPage.startsWith("/"))
            // throw new IllegalArgumentException
            // ("Error Page resource path must start with a '/'");
            this.errorPage = RequestUtil.URLDecode(errorPage);
        }
    
        /**
         * 如果使用的是基於表單的身份驗證方法 登錄頁面的URL
         */
        private String loginPage = null;
    
        /**
         * 
         * 
         * <p>
         * Title: getLoginPage
         * </p>
         * 
         * @date 2018年11月21日 下午10:14:37
         * 
         *       <p>
         *       功能描述: 返回 如果使用的是基於表單的身份驗證方法 登錄頁面的URL
         *       </p>
         * 
         * @return
         */
        public String getLoginPage() {
            return (this.loginPage);
        }
    
        public void setLoginPage(String loginPage) {
            // if ((loginPage == null) || !loginPage.startsWith("/"))
            // throw new IllegalArgumentException
            // ("Login Page resource path must start with a '/'");
            this.loginPage = RequestUtil.URLDecode(loginPage);
        }
    
        /**
         * 登錄配置的領域對象名字
         */
        private String realmName = null;
    
        public String getRealmName() {
            return (this.realmName);
        }
    
        public void setRealmName(String realmName) {
            this.realmName = realmName;
        }
    
        // --------------------------------------------------------- Public Methods
    
        /**
         * Return a String representation of this object.
         */
        public String toString() {
    
            StringBuffer sb = new StringBuffer("LoginConfig[");
            sb.append("authMethod=");
            sb.append(authMethod);
            if (realmName != null) {
                sb.append(", realmName=");
                sb.append(realmName);
            }
            if (loginPage != null) {
                sb.append(", loginPage=");
                sb.append(loginPage);
            }
            if (errorPage != null) {
                sb.append(", errorPage=");
                sb.append(errorPage);
            }
            sb.append("]");
            return (sb.toString());
    
        }
    
    }

    Authenticator接口

  • 驗證器是 org.apache.catalina.Authenticator接口的實例,Authenticator接口本身並沒有聲明任何方法,只是起到了一個標記的作用,這樣其他的組件就可以使用 instanceof 關鍵字來檢查某個組件是不是一個驗證器,
  • Ctalina提供了 Authenticator接口的一個基本實現,org.apache.catalina.authenticator.AuthenticatorBase,其除了實現了 Authenticator接口之外,還擴展繼承了 org.apache.catalina.valves.ValveBase類,也就說AuthenticatorBase類也會一個閥,在Catalina.authenticator包下有很多實現類,包括 BasicAuthenticator (可以用來支持基本的身份驗證)、FormAuthenticator類(提供了基於表單的身份驗證)、DigestAuthenticator類(提供了基於信息摘要的身份驗證) 和SSLAuthenticator類(用於對SSL進行身份驗證),此外當Tomcat用戶沒有指定驗證方法名時,NonLoginAuthenticator類用於對來訪者的身份進行驗證,NonLoginAuthenticator類實現的驗證器只會檢查安全限制,而不會涉及到用戶身份驗證,驗證器的只要工作是對用戶進行身份驗證,,因此,AuthenticatorBase類的invoke方法調用authenticator抽象方法,后者的實現依賴於子類,下面來看下AuthenticatorBase類的實現
  • package org.apache.catalina.authenticator;
    
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.security.Principal;
    import java.util.Random;
    import javax.servlet.ServletException;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import org.apache.catalina.Authenticator;
    import org.apache.catalina.Container;
    import org.apache.catalina.Context;
    import org.apache.catalina.HttpRequest;
    import org.apache.catalina.HttpResponse;
    import org.apache.catalina.Lifecycle;
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.LifecycleListener;
    import org.apache.catalina.Logger;
    import org.apache.catalina.Manager;
    import org.apache.catalina.Pipeline;
    import org.apache.catalina.Realm;
    import org.apache.catalina.Request;
    import org.apache.catalina.Response;
    import org.apache.catalina.Session;
    import org.apache.catalina.Valve;
    import org.apache.catalina.ValveContext;
    import org.apache.catalina.deploy.LoginConfig;
    import org.apache.catalina.deploy.SecurityConstraint;
    import org.apache.catalina.util.LifecycleSupport;
    import org.apache.catalina.util.StringManager;
    import org.apache.catalina.valves.ValveBase;
    
    /**
     * 
     * <p>
     * Title:AuthenticatorBase.java
     * </p>
     * 
     * <p>
     * 類功能描述: {@link Authenticator}接口的基本實現類,該類除了實現了 {@link Authenticator}接口之外,還擴展了
     * {@link ValveBase}類,這也就是說,{@link AuthenticatorBase}類也是一個閥,該抽象類
     * 在{@code Catalina}包下有很多實現類,包括了{@link BasicAuthenticator}(可以用來支持基本的身份驗證) 類,
     * {@link FormAuthenticator}(提供了基於表單的的身份驗證),{@link DigestAuthenticator}
     * (提供了基於信息摘要的身份驗證),和{@link SSLAuthenticator}(用於對SSL進行身份驗證),此外,當{@code Tomcat}
     * 用戶沒有 指定驗證方法名時,{@link NonLoginAuthenticator}類用於對來訪者的身份進行驗證,
     * {@link NonLoginAuthenticator}類實現的驗證器, 只會檢查安全限制,不會涉及用戶身份的驗證,
     * </p>
     * 
     * <dd>驗證器的重要工作是對於用戶進行身份驗證,該類中的{@code invoke }方法中,會調用
     * {@code authenticate}抽象方法時,后者的實現依賴於 {@link AuthenticatorBase} 的子類.</dd>
     * 
     * <dd>安裝驗證器閥:在部署描述器中,{@code login-config}元素僅能出現一次,{@code login-config}元素包含一個
     * {@code auth-method}元素來指定 身份驗證方法,也就是說
     * 一個{@link Context}容器實例只能有一個{@link LoginConfig}實例和利用一個驗證類的實現,</dd>
     * <dd>使用{@link AuthenticatorBase}類的的哪一個子類來作為{@link Context}實例中驗證器閥依賴於部署描述器中
     * {@code auth-method}元素的值,{@code auth-method : 驗證器類},
     * {@code BASIC :}{@link BasicAuthenticator},{@code FORM:}
     * {@link FormAuthenticator},{@code DIGEST:}{@link DigestAuthenticator},
     * {@code CLIENT-CERT:}{@link SSLAuthenticator}</dd>
     * <dd>若沒有設置{@code auth-method}元素,則{@link LoginConfig}
     * 對象的{@code auth-method}屬性值默認為{@code NONE}
     * 這時會使用{@link NonLoginAuthenticator}進行安全驗證</dd>
     * 
     * 
     * @author 陳東
     * @date 2018年11月22日 下午7:22:47
     * @version 1.0
     */
    public abstract class AuthenticatorBase extends ValveBase implements Authenticator, Lifecycle {
    
        // ----------------------------------------------------- Instance Variables
    
        /**
         * 
         * 如果不能使用請求的摘要,則使用默認的 MD5加密算法
         */
        protected static final String DEFAULT_ALGORITHM = "MD5";
    
        /**
         * 生成會話標識符時要包括的隨機字節數。.
         */
        protected static final int SESSION_ID_BYTES = 16;
    
        /**
         * 在生成會話標識符時使用的消息摘要算法。這必須是平台上的<code>java.security.MessageDigest</code>
         * 類支持的算法。
         */
        protected String algorithm = DEFAULT_ALGORITHM;
    
        /**
         * Should we cache authenticated Principals if the request is part of an
         * HTTP session?
         */
        protected boolean cache = true;
    
        /**
         * The Context to which this Valve is attached.
         */
        protected Context context = null;
    
        /**
         * The debugging detail level for this component.
         */
        protected int debug = 0;
    
        /**
         * Return the MessageDigest implementation to be used when creating session
         * identifiers.
         */
        protected MessageDigest digest = null;
    
        /**
         * A String initialization parameter used to increase the entropy of the
         * initialization of our random number generator.
         */
        protected String entropy = null;
    
        /**
         * Descriptive information about this implementation.
         */
        protected static final String info = "org.apache.catalina.authenticator.AuthenticatorBase/1.0";
    
        /**
         * The lifecycle event support for this component.
         */
        protected LifecycleSupport lifecycle = new LifecycleSupport(this);
    
        /**
         * A random number generator to use when generating session identifiers.
         */
        protected Random random = null;
    
        /**
         * The Java class name of the random number generator class to be used when
         * generating session identifiers.
         */
        protected String randomClass = "java.security.SecureRandom";
    
        /**
         * The string manager for this package.
         */
        protected static final StringManager sm = StringManager.getManager(Constants.Package);
    
        /**
         * The SingleSignOn implementation in our request processing chain, if there
         * is one.
         */
        protected SingleSignOn sso = null;
    
        /**
         * Has this component been started?
         */
        protected boolean started = false;
    
        // ------------------------------------------------------------- Properties
    
        /**
         * Return the message digest algorithm for this Manager.
         */
        public String getAlgorithm() {
    
            return (this.algorithm);
    
        }
    
        /**
         * Set the message digest algorithm for this Manager.
         *
         * @param algorithm
         *            The new message digest algorithm
         */
        public void setAlgorithm(String algorithm) {
    
            this.algorithm = algorithm;
    
        }
    
        /**
         * Return the cache authenticated Principals flag.
         */
        public boolean getCache() {
    
            return (this.cache);
    
        }
    
        /**
         * Set the cache authenticated Principals flag.
         *
         * @param cache
         *            The new cache flag
         */
        public void setCache(boolean cache) {
    
            this.cache = cache;
    
        }
    
        /**
         * Return the Container to which this Valve is attached.
         */
        public Container getContainer() {
    
            return (this.context);
    
        }
    
        /**
         * Set the Container to which this Valve is attached.
         *
         * @param container
         *            The container to which we are attached
         */
        public void setContainer(Container container) {
    
            if (!(container instanceof Context))
                throw new IllegalArgumentException(sm.getString("authenticator.notContext"));
    
            super.setContainer(container);
            this.context = (Context) container;
    
        }
    
        /**
         * Return the debugging detail level for this component.
         */
        public int getDebug() {
    
            return (this.debug);
    
        }
    
        /**
         * Set the debugging detail level for this component.
         *
         * @param debug
         *            The new debugging detail level
         */
        public void setDebug(int debug) {
    
            this.debug = debug;
    
        }
    
        /**
         * Return the entropy increaser value, or compute a semi-useful value if
         * this String has not yet been set.
         */
        public String getEntropy() {
    
            // Calculate a semi-useful value if this has not been set
            if (this.entropy == null)
                setEntropy(this.toString());
    
            return (this.entropy);
    
        }
    
        /**
         * Set the entropy increaser value.
         *
         * @param entropy
         *            The new entropy increaser value
         */
        public void setEntropy(String entropy) {
    
            this.entropy = entropy;
    
        }
    
        /**
         * Return descriptive information about this Valve implementation.
         */
        public String getInfo() {
    
            return (this.info);
    
        }
    
        /**
         * Return the random number generator class name.
         */
        public String getRandomClass() {
    
            return (this.randomClass);
    
        }
    
        /**
         * Set the random number generator class name.
         *
         * @param randomClass
         *            The new random number generator class name
         */
        public void setRandomClass(String randomClass) {
    
            this.randomClass = randomClass;
    
        }
    
        // --------------------------------------------------------- Public Methods
    
        /**
         * 在相關聯的Context容器的Web應用程序部署中強制執行安全限制。
         *
         * @param request
         *            Request to be processed
         * @param response
         *            Response to be processed
         * @param context
         *            The valve context used to invoke the next valve in the current
         *            processing pipeline
         *
         * @exception IOException
         *                if an input/output error occurs
         * @exception ServletException
         *                if thrown by a processing element
         */
        public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
    
            // If this is not an HTTP request, do nothing
            if (!(request instanceof HttpRequest) || !(response instanceof HttpResponse)) {
                context.invokeNext(request, response);
                return;
            }
            if (!(request.getRequest() instanceof HttpServletRequest)
                    || !(response.getResponse() instanceof HttpServletResponse)) {
                context.invokeNext(request, response);
                return;
            }
            HttpRequest hrequest = (HttpRequest) request;
            HttpResponse hresponse = (HttpResponse) response;
            if (debug >= 1)
                log("Security checking request " + ((HttpServletRequest) request.getRequest()).getMethod() + " "
                        + ((HttpServletRequest) request.getRequest()).getRequestURI());
            LoginConfig config = this.context.getLoginConfig();
    
            // 我們有一個緩存的認證主體來記錄嗎?
            if (cache) {
                Principal principal = ((HttpServletRequest) request.getRequest()).getUserPrincipal();
                if (principal == null) {
                    Session session = getSession(hrequest);
                    if (session != null) {
                        principal = session.getPrincipal();
                        if (principal != null) {
                            if (debug >= 1)
                                log("We have cached auth type " + session.getAuthType() + " for principal "
                                        + session.getPrincipal());
                            hrequest.setAuthType(session.getAuthType());
                            hrequest.setUserPrincipal(principal);
                        }
                    }
                }
            }
    
            // Special handling for form-based logins to deal with the case
            // where the login form (and therefore the "j_security_check" URI
            // to which it submits) might be outside the secured area
            String contextPath = this.context.getPath();
            String requestURI = hrequest.getDecodedRequestURI();
            if (requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION)) {
                if (!authenticate(hrequest, hresponse, config)) {
                    if (debug >= 1)
                        log(" Failed authenticate() test");
                    return;
                }
            }
    
            // Is this request URI subject to a security constraint?
            SecurityConstraint constraint = findConstraint(hrequest);
            if ((constraint == null) /*
                                         * && (!Constants.FORM_METHOD.equals(config.
                                         * getAuthMethod()))
                                         */ ) {
                if (debug >= 1)
                    log(" Not subject to any constraint");
                context.invokeNext(request, response);
                return;
            }
            if ((debug >= 1) && (constraint != null))
                log(" Subject to constraint " + constraint);
    
            // Make sure that constrained resources are not cached by web proxies
            // or browsers as caching can provide a security hole
            if (!(((HttpServletRequest) hrequest.getRequest()).isSecure())) {
                HttpServletResponse sresponse = (HttpServletResponse) response.getResponse();
                sresponse.setHeader("Pragma", "No-cache");
                sresponse.setHeader("Cache-Control", "no-cache");
                sresponse.setDateHeader("Expires", 1);
            }
    
            // Enforce any user data constraint for this security constraint
            if (debug >= 1)
                log(" Calling checkUserData()");
            if (!checkUserData(hrequest, hresponse, constraint)) {
                if (debug >= 1)
                    log(" Failed checkUserData() test");
                // ASSERT: Authenticator already set the appropriate
                // HTTP status code, so we do not have to do anything special
                return;
            }
    
            // Authenticate based upon the specified login configuration
            if (constraint.getAuthConstraint()) {
                if (debug >= 1)
                    log(" Calling authenticate()");
                if (!authenticate(hrequest, hresponse, config)) {
                    if (debug >= 1)
                        log(" Failed authenticate() test");
                    // ASSERT: Authenticator already set the appropriate
                    // HTTP status code, so we do not have to do anything special
                    return;
                }
            }
    
            // Perform access control based on the specified role(s)
            if (constraint.getAuthConstraint()) {
                if (debug >= 1)
                    log(" Calling accessControl()");
                if (!accessControl(hrequest, hresponse, constraint)) {
                    if (debug >= 1)
                        log(" Failed accessControl() test");
                    // ASSERT: AccessControl method has already set the appropriate
                    // HTTP status code, so we do not have to do anything special
                    return;
                }
            }
    
            // Any and all specified constraints have been satisfied
            if (debug >= 1)
                log(" Successfully passed all security constraints");
            context.invokeNext(request, response);
    
        }
    
        // ------------------------------------------------------ Protected Methods
    
        /**
         * Perform access control based on the specified authorization constraint.
         * Return <code>true</code> if this constraint is satisfied and processing
         * should continue, or <code>false</code> otherwise.
         *
         * @param request
         *            Request we are processing
         * @param response
         *            Response we are creating
         * @param constraint
         *            Security constraint we are enforcing
         *
         * @exception IOException
         *                if an input/output error occurs
         */
        protected boolean accessControl(HttpRequest request, HttpResponse response, SecurityConstraint constraint)
                throws IOException {
    
            if (constraint == null)
                return (true);
    
            // Specifically allow access to the form login and form error pages
            // and the "j_security_check" action
            LoginConfig config = context.getLoginConfig();
            if ((config != null) && (Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
                String requestURI = request.getDecodedRequestURI();
                String loginPage = context.getPath() + config.getLoginPage();
                if (loginPage.equals(requestURI)) {
                    if (debug >= 1)
                        log(" Allow access to login page " + loginPage);
                    return (true);
                }
                String errorPage = context.getPath() + config.getErrorPage();
                if (errorPage.equals(requestURI)) {
                    if (debug >= 1)
                        log(" Allow access to error page " + errorPage);
                    return (true);
                }
                if (requestURI.endsWith(Constants.FORM_ACTION)) {
                    if (debug >= 1)
                        log(" Allow access to username/password submission");
                    return (true);
                }
            }
    
            // Which user principal have we already authenticated?
            Principal principal = ((HttpServletRequest) request.getRequest()).getUserPrincipal();
            if (principal == null) {
                if (debug >= 2)
                    log("  No user authenticated, cannot grant access");
                ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                        sm.getString("authenticator.notAuthenticated"));
                return (false);
            }
    
            // Check each role included in this constraint
            Realm realm = context.getRealm();
            String roles[] = constraint.findAuthRoles();
            if (roles == null)
                roles = new String[0];
    
            if (constraint.getAllRoles())
                return (true);
            if ((roles.length == 0) && (constraint.getAuthConstraint())) {
                ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
                        sm.getString("authenticator.forbidden"));
                return (false); // No listed roles means no access at all
            }
            for (int i = 0; i < roles.length; i++) {
                if (realm.hasRole(principal, roles[i]))
                    return (true);
            }
    
            // Return a "Forbidden" message denying access to this resource
            ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
                    sm.getString("authenticator.forbidden"));
            return (false);
    
        }
    
        /**
         * Associate the specified single sign on identifier with the specified
         * Session.
         *
         * @param ssoId
         *            Single sign on identifier
         * @param session
         *            Session to be associated
         */
        protected void associate(String ssoId, Session session) {
    
            if (sso == null)
                return;
            sso.associate(ssoId, session);
    
        }
    
        /**
         * Authenticate the user making this request, based on the specified login
         * configuration. Return <code>true</code> if any specified constraint has
         * been satisfied, or <code>false</code> if we have created a response
         * challenge already.
         *
         * @param request
         *            Request we are processing
         * @param response
         *            Response we are creating
         * @param login
         *            Login configuration describing how authentication should be
         *            performed
         *
         * @exception IOException
         *                if an input/output error occurs
         */
        protected abstract boolean authenticate(HttpRequest request, HttpResponse response, LoginConfig config)
                throws IOException;
    
        /**
         * Enforce any user data constraint required by the security constraint
         * guarding this request URI. Return <code>true</code> if this constraint
         * was not violated and processing should continue, or <code>false</code> if
         * we have created a response already.
         *
         * @param request
         *            Request we are processing
         * @param response
         *            Response we are creating
         * @param constraint
         *            Security constraint being checked
         *
         * @exception IOException
         *                if an input/output error occurs
         */
        protected boolean checkUserData(HttpRequest request, HttpResponse response, SecurityConstraint constraint)
                throws IOException {
    
            // Is there a relevant user data constraint?
            if (constraint == null) {
                if (debug >= 2)
                    log("  No applicable security constraint defined");
                return (true);
            }
            String userConstraint = constraint.getUserConstraint();
            if (userConstraint == null) {
                if (debug >= 2)
                    log("  No applicable user data constraint defined");
                return (true);
            }
            if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
                if (debug >= 2)
                    log("  User data constraint has no restrictions");
                return (true);
            }
    
            // Validate the request against the user data constraint
            if (request.getRequest().isSecure()) {
                if (debug >= 2)
                    log("  User data constraint already satisfied");
                return (true);
            }
    
            // Initialize variables we need to determine the appropriate action
            HttpServletRequest hrequest = (HttpServletRequest) request.getRequest();
            HttpServletResponse hresponse = (HttpServletResponse) response.getResponse();
            int redirectPort = request.getConnector().getRedirectPort();
    
            // Is redirecting disabled?
            if (redirectPort <= 0) {
                if (debug >= 2)
                    log("  SSL redirect is disabled");
                hresponse.sendError(HttpServletResponse.SC_FORBIDDEN, hrequest.getRequestURI());
                return (false);
            }
    
            // Redirect to the corresponding SSL port
            String protocol = "https";
            String host = hrequest.getServerName();
            StringBuffer file = new StringBuffer(hrequest.getRequestURI());
            String requestedSessionId = hrequest.getRequestedSessionId();
            if ((requestedSessionId != null) && hrequest.isRequestedSessionIdFromURL()) {
                file.append(";jsessionid=");
                file.append(requestedSessionId);
            }
            String queryString = hrequest.getQueryString();
            if (queryString != null) {
                file.append('?');
                file.append(queryString);
            }
            URL url = null;
            try {
                url = new URL(protocol, host, redirectPort, file.toString());
                if (debug >= 2)
                    log("  Redirecting to " + url.toString());
                hresponse.sendRedirect(url.toString());
                return (false);
            } catch (MalformedURLException e) {
                if (debug >= 2)
                    log("  Cannot create new URL", e);
                hresponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, hrequest.getRequestURI());
                return (false);
            }
    
        }
    
        /**
         * Return the SecurityConstraint configured to guard the request URI for
         * this request, or <code>null</code> if there is no such constraint.
         *
         * @param request
         *            Request we are processing
         */
        protected SecurityConstraint findConstraint(HttpRequest request) {
    
            // Are there any defined security constraints?
            SecurityConstraint constraints[] = context.findConstraints();
            if ((constraints == null) || (constraints.length == 0)) {
                if (debug >= 2)
                    log("  No applicable constraints defined");
                return (null);
            }
    
            // Check each defined security constraint
            HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
            String uri = request.getDecodedRequestURI();
            String contextPath = hreq.getContextPath();
            if (contextPath.length() > 0)
                uri = uri.substring(contextPath.length());
            String method = hreq.getMethod();
            for (int i = 0; i < constraints.length; i++) {
                if (debug >= 2)
                    log("  Checking constraint '" + constraints[i] + "' against " + method + " " + uri + " --> "
                            + constraints[i].included(uri, method));
                if (constraints[i].included(uri, method))
                    return (constraints[i]);
            }
    
            // No applicable security constraint was found
            if (debug >= 2)
                log("  No applicable constraint located");
            return (null);
    
        }
    
        /**
         * Generate and return a new session identifier for the cookie that
         * identifies an SSO principal.
         */
        protected synchronized String generateSessionId() {
    
            // Generate a byte array containing a session identifier
            Random random = getRandom();
            byte bytes[] = new byte[SESSION_ID_BYTES];
            getRandom().nextBytes(bytes);
            bytes = getDigest().digest(bytes);
    
            // Render the result as a String of hexadecimal digits
            StringBuffer result = new StringBuffer();
            for (int i = 0; i < bytes.length; i++) {
                byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);
                byte b2 = (byte) (bytes[i] & 0x0f);
                if (b1 < 10)
                    result.append((char) ('0' + b1));
                else
                    result.append((char) ('A' + (b1 - 10)));
                if (b2 < 10)
                    result.append((char) ('0' + b2));
                else
                    result.append((char) ('A' + (b2 - 10)));
            }
            return (result.toString());
    
        }
    
        /**
         * Return the MessageDigest object to be used for calculating session
         * identifiers. If none has been created yet, initialize one the first time
         * this method is called.
         */
        protected synchronized MessageDigest getDigest() {
    
            if (this.digest == null) {
                try {
                    this.digest = MessageDigest.getInstance(algorithm);
                } catch (NoSuchAlgorithmException e) {
                    try {
                        this.digest = MessageDigest.getInstance(DEFAULT_ALGORITHM);
                    } catch (NoSuchAlgorithmException f) {
                        this.digest = null;
                    }
                }
            }
    
            return (this.digest);
    
        }
    
        /**
         * Return the random number generator instance we should use for generating
         * session identifiers. If there is no such generator currently defined,
         * construct and seed a new one.
         */
        protected synchronized Random getRandom() {
    
            if (this.random == null) {
                try {
                    Class clazz = Class.forName(randomClass);
                    this.random = (Random) clazz.newInstance();
                    long seed = System.currentTimeMillis();
                    char entropy[] = getEntropy().toCharArray();
                    for (int i = 0; i < entropy.length; i++) {
                        long update = ((byte) entropy[i]) << ((i % 8) * 8);
                        seed ^= update;
                    }
                    this.random.setSeed(seed);
                } catch (Exception e) {
                    this.random = new java.util.Random();
                }
            }
    
            return (this.random);
    
        }
    
        /**
         * Return the internal Session that is associated with this HttpRequest, or
         * <code>null</code> if there is no such Session.
         *
         * @param request
         *            The HttpRequest we are processing
         */
        protected Session getSession(HttpRequest request) {
    
            return (getSession(request, false));
    
        }
    
        /**
         * Return the internal Session that is associated with this HttpRequest,
         * possibly creating a new one if necessary, or <code>null</code> if there
         * is no such session and we did not create one.
         *
         * @param request
         *            The HttpRequest we are processing
         * @param create
         *            Should we create a session if needed?
         */
        protected Session getSession(HttpRequest request, boolean create) {
    
            HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
            HttpSession hses = hreq.getSession(create);
            if (hses == null)
                return (null);
            Manager manager = context.getManager();
            if (manager == null)
                return (null);
            else {
                try {
                    return (manager.findSession(hses.getId()));
                } catch (IOException e) {
                    return (null);
                }
            }
    
        }
    
        /**
         * Log a message on the Logger associated with our Container (if any).
         *
         * @param message
         *            Message to be logged
         */
        protected void log(String message) {
    
            Logger logger = context.getLogger();
            if (logger != null)
                logger.log("Authenticator[" + context.getPath() + "]: " + message);
            else
                System.out.println("Authenticator[" + context.getPath() + "]: " + message);
    
        }
    
        /**
         * Log a message on the Logger associated with our Container (if any).
         *
         * @param message
         *            Message to be logged
         * @param throwable
         *            Associated exception
         */
        protected void log(String message, Throwable throwable) {
    
            Logger logger = context.getLogger();
            if (logger != null)
                logger.log("Authenticator[" + context.getPath() + "]: " + message, throwable);
            else {
                System.out.println("Authenticator[" + context.getPath() + "]: " + message);
                throwable.printStackTrace(System.out);
            }
    
        }
    
        /**
         * Register an authenticated Principal and authentication type in our
         * request, in the current session (if there is one), and with our
         * SingleSignOn valve, if there is one. Set the appropriate cookie to be
         * returned.
         *
         * @param request
         *            The servlet request we are processing
         * @param response
         *            The servlet response we are generating
         * @param principal
         *            The authenticated Principal to be registered
         * @param authType
         *            The authentication type to be registered
         * @param username
         *            Username used to authenticate (if any)
         * @param password
         *            Password used to authenticate (if any)
         */
        protected void register(HttpRequest request, HttpResponse response, Principal principal, String authType,
                String username, String password) {
    
            if (debug >= 1)
                log("Authenticated '" + principal.getName() + "' with type '" + authType + "'");
    
            // Cache the authentication information in our request
            request.setAuthType(authType);
            request.setUserPrincipal(principal);
    
            // Cache the authentication information in our session, if any
            if (cache) {
                Session session = getSession(request, false);
                if (session != null) {
                    session.setAuthType(authType);
                    session.setPrincipal(principal);
                    if (username != null)
                        session.setNote(Constants.SESS_USERNAME_NOTE, username);
                    else
                        session.removeNote(Constants.SESS_USERNAME_NOTE);
                    if (password != null)
                        session.setNote(Constants.SESS_PASSWORD_NOTE, password);
                    else
                        session.removeNote(Constants.SESS_PASSWORD_NOTE);
                }
            }
    
            // Construct a cookie to be returned to the client
            if (sso == null)
                return;
            HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
            HttpServletResponse hres = (HttpServletResponse) response.getResponse();
            String value = generateSessionId();
            Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, value);
            cookie.setMaxAge(-1);
            cookie.setPath("/");
            hres.addCookie(cookie);
    
            // Register this principal with our SSO valve
            sso.register(value, principal, authType, username, password);
            request.setNote(Constants.REQ_SSOID_NOTE, value);
    
        }
    
        // ------------------------------------------------------ Lifecycle Methods
    
        /**
         * Add a lifecycle event listener to this component.
         *
         * @param listener
         *            The listener to add
         */
        public void addLifecycleListener(LifecycleListener listener) {
    
            lifecycle.addLifecycleListener(listener);
    
        }
    
        /**
         * Get the lifecycle listeners associated with this lifecycle. If this
         * Lifecycle has no listeners registered, a zero-length array is returned.
         */
        public LifecycleListener[] findLifecycleListeners() {
    
            return lifecycle.findLifecycleListeners();
    
        }
    
        /**
         * Remove a lifecycle event listener from this component.
         *
         * @param listener
         *            The listener to remove
         */
        public void removeLifecycleListener(LifecycleListener listener) {
    
            lifecycle.removeLifecycleListener(listener);
    
        }
    
        /**
         * Prepare for the beginning of active use of the public methods of this
         * component. This method should be called after <code>configure()</code>,
         * and before any of the public methods of the component are utilized.
         *
         * @exception LifecycleException
         *                if this component detects a fatal error that prevents this
         *                component from being used
         */
        public void start() throws LifecycleException {
    
            // Validate and update our current component state
            if (started)
                throw new LifecycleException(sm.getString("authenticator.alreadyStarted"));
            lifecycle.fireLifecycleEvent(START_EVENT, null);
            if ("org.apache.catalina.core.StandardContext".equals(context.getClass().getName())) {
                try {
                    Class paramTypes[] = new Class[0];
                    Object paramValues[] = new Object[0];
                    Method method = context.getClass().getMethod("getDebug", paramTypes);
                    Integer result = (Integer) method.invoke(context, paramValues);
                    setDebug(result.intValue());
                } catch (Exception e) {
                    log("Exception getting debug value", e);
                }
            }
            started = true;
    
            // Look up the SingleSignOn implementation in our request processing
            // path, if there is one
            Container parent = context.getParent();
            while ((sso == null) && (parent != null)) {
                if (!(parent instanceof Pipeline)) {
                    parent = parent.getParent();
                    continue;
                }
                Valve valves[] = ((Pipeline) parent).getValves();
                for (int i = 0; i < valves.length; i++) {
                    if (valves[i] instanceof SingleSignOn) {
                        sso = (SingleSignOn) valves[i];
                        break;
                    }
                }
                if (sso == null)
                    parent = parent.getParent();
            }
            if (debug >= 1) {
                if (sso != null)
                    log("Found SingleSignOn Valve at " + sso);
                else
                    log("No SingleSignOn Valve is present");
            }
    
        }
    
        /**
         * Gracefully terminate the active use of the public methods of this
         * component. This method should be the last one called on a given instance
         * of this component.
         *
         * @exception LifecycleException
         *                if this component detects a fatal error that needs to be
         *                reported
         */
        public void stop() throws LifecycleException {
    
            // Validate and update our current component state
            if (!started)
                throw new LifecycleException(sm.getString("authenticator.notStarted"));
            lifecycle.fireLifecycleEvent(STOP_EVENT, null);
            started = false;
    
            sso = null;
    
        }
    
    }

    那么來捋順一下 上面幾個對象之間的關系

  • 領域Realm對象,是與Context容器一對一進行關聯的對象,其包含了 所有有效的用戶的用戶名和密碼,Realm究竟在tomcat中運用哪一個子類的實現,依靠配置文件中的配置,
  • 主體對象GenericPrincipal類,是在領域對象的驗證方法 成功后返回的 封裝了 用戶信息的對象。
  • LoginConfig登錄配置類,保存了 領域名字,和所要使用身份驗證方法(tomcat啟動servlet容器時會根據方法名 來動態加載相應 的驗證器閥)
  • 那我們來看下 為StandardContext類在啟動時配置各種屬性的ContextConfig類中
  • 的實現,其繼承了LifecycleListener接口所以會被Context在啟動時被監聽觸發,
  • public void lifecycleEvent(LifecycleEvent event) {
    
            // Identify the context we are associated with
            try {
                context = (Context) event.getLifecycle();
                if (context instanceof StandardContext) {
                    int contextDebug = ((StandardContext) context).getDebug();
                    if (contextDebug > this.debug)
                        this.debug = contextDebug;
                }
            } catch (ClassCastException e) {
                log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
                return;
            }
    
            // Process the event that has occurred
            if (event.getType().equals(Lifecycle.START_EVENT))
                start();
            else if (event.getType().equals(Lifecycle.STOP_EVENT))
                stop();
    
        }
    private synchronized void start() {
    
            if (debug > 0)
                log(sm.getString("contextConfig.start"));
            context.setConfigured(false);
            ok = true;
    
            // Set properties based on DefaultContext
            Container container = context.getParent();
            if (!context.getOverride()) {
                if (container instanceof Host) {
                    ((Host) container).importDefaultContext(context);
                    container = container.getParent();
                }
                if (container instanceof Engine) {
                    ((Engine) container).importDefaultContext(context);
                }
            }
    
            // Process the default and application web.xml files
            defaultConfig();
            applicationConfig();
            if (ok) {
                validateSecurityRoles();
            }
    
            // Scan tag library descriptor files for additional listener classes
            if (ok) {
                try {
                    tldScan();
                } catch (Exception e) {
                    log(e.getMessage(), e);
                    ok = false;
                }
            }
    
            // Configure a certificates exposer valve, if required
            if (ok)
                certificatesConfig();
    
            // Configure an authenticator if we need one
            if (ok)
    //這個就是配置安全屬性的方法 authenticatorConfig();
    // Dump the contents of this pipeline if requested if ((debug >= 1) && (context instanceof ContainerBase)) { log("Pipline Configuration:"); Pipeline pipeline = ((ContainerBase) context).getPipeline(); Valve valves[] = null; if (pipeline != null) valves = pipeline.getValves(); if (valves != null) { for (int i = 0; i < valves.length; i++) { log(" " + valves[i].getInfo()); } } log("======================"); } // Make our application available if no problems were encountered if (ok) context.setConfigured(true); else { log(sm.getString("contextConfig.unavailable")); context.setConfigured(false); } }
    private synchronized void authenticatorConfig() {
    
            // Does this Context require an Authenticator?
            SecurityConstraint constraints[] = context.findConstraints();
            if ((constraints == null) || (constraints.length == 0))
                return;
            LoginConfig loginConfig = context.getLoginConfig();
            if (loginConfig == null) {
                loginConfig = new LoginConfig("NONE", null, null, null);
                context.setLoginConfig(loginConfig);
            }
    
            // Has an authenticator been configured already?
            if (context instanceof Authenticator)
                return;
            if (context instanceof ContainerBase) {
                Pipeline pipeline = ((ContainerBase) context).getPipeline();
                if (pipeline != null) {
                    Valve basic = pipeline.getBasic();
                    if ((basic != null) && (basic instanceof Authenticator))
                        return;
                    Valve valves[] = pipeline.getValves();
                    for (int i = 0; i < valves.length; i++) {
                        if (valves[i] instanceof Authenticator)
                            return;
                    }
                }
            } else {
                return; // Cannot install a Valve even if it would be needed
            }
    
            // Has a Realm been configured for us to authenticate against?
            if (context.getRealm() == null) {
                log(sm.getString("contextConfig.missingRealm"));
                ok = false;
                return;
            }
    
            // Load our mapping properties if necessary
            if (authenticators == null) {
                try {
                    authenticators = ResourceBundle.getBundle("org.apache.catalina.startup.Authenticators");
                } catch (MissingResourceException e) {
                    log(sm.getString("contextConfig.authenticatorResources"), e);
                    ok = false;
                    return;
                }
            }
    
            // Identify the class name of the Valve we should configure
            String authenticatorName = null;
            try {
                // 根據用戶配置的 驗證方法 取得 驗證器閥的類名
                authenticatorName = authenticators.getString(loginConfig.getAuthMethod());
            } catch (MissingResourceException e) {
                authenticatorName = null;
            }
            if (authenticatorName == null) {
                log(sm.getString("contextConfig.authenticatorMissing", loginConfig.getAuthMethod()));
                ok = false;
                return;
            }
    
            // Instantiate and install an Authenticator of the requested class
            Valve authenticator = null;
            try {
                // 實例化 並添加到 管道中
                Class authenticatorClass = Class.forName(authenticatorName);
                authenticator = (Valve) authenticatorClass.newInstance();
                if (context instanceof ContainerBase) {
                    Pipeline pipeline = ((ContainerBase) context).getPipeline();
                    if (pipeline != null) {
                        ((ContainerBase) context).addValve(authenticator);
                        log(sm.getString("contextConfig.authenticatorConfigured", loginConfig.getAuthMethod()));
                    }
                }
            } catch (Throwable t) {
                log(sm.getString("contextConfig.authenticatorInstantiate", authenticatorName), t);
                ok = false;
            }
    
        }

     

  • Authenticator接口 是驗證器的父類,只起到標記該實現是驗證器的作用,真正實現功能的是AuthenticatorBase類,且器繼承了閥的接口,所以在管道執行閥的時候會被執行,在執行中根據在Context容器在啟動時 ,根據LoginConfig中的驗證方法實例化的子類,
  • 展示下AuthenticatorBase類中invoke方法,在管道被執行的時候 會執行該閥的invoke方法,在其中 調用了抽象驗證方法authenticate,這個就需要前面被實例化的子類來實現了,
  •   1 public void invoke(Request request, Response response, ValveContext context) throws IOException, ServletException {
      2 
      3         // If this is not an HTTP request, do nothing
      4         if (!(request instanceof HttpRequest) || !(response instanceof HttpResponse)) {
      5             context.invokeNext(request, response);
      6             return;
      7         }
      8         if (!(request.getRequest() instanceof HttpServletRequest)
      9                 || !(response.getResponse() instanceof HttpServletResponse)) {
     10             context.invokeNext(request, response);
     11             return;
     12         }
     13         HttpRequest hrequest = (HttpRequest) request;
     14         HttpResponse hresponse = (HttpResponse) response;
     15         if (debug >= 1)
     16             log("Security checking request " + ((HttpServletRequest) request.getRequest()).getMethod() + " "
     17                     + ((HttpServletRequest) request.getRequest()).getRequestURI());
     18         LoginConfig config = this.context.getLoginConfig();
     19 
     20         // 我們有一個緩存的認證主體來記錄嗎? 如果請求是 session會話 看下session中是否已經存在驗證過的主體
     21         if (cache) {
     22             Principal principal = ((HttpServletRequest) request.getRequest()).getUserPrincipal();
     23             if (principal == null) {
     24                 Session session = getSession(hrequest);
     25                 if (session != null) {
     26                     principal = session.getPrincipal();
     27                     if (principal != null) {
     28                         if (debug >= 1)
     29                             log("We have cached auth type " + session.getAuthType() + " for principal "
     30                                     + session.getPrincipal());
     31                         hrequest.setAuthType(session.getAuthType());
     32                         hrequest.setUserPrincipal(principal);
     33                     }
     34                 }
     35             }
     36         }
     37 
     38         // Special handling for form-based logins to deal with the case
     39         // where the login form (and therefore the "j_security_check" URI
     40         // to which it submits) might be outside the secured area
     41         String contextPath = this.context.getPath();
     42         String requestURI = hrequest.getDecodedRequestURI();
     43         if (requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION)) {
     44             if (!authenticate(hrequest, hresponse, config)) {
     45                 if (debug >= 1)
     46                     log(" Failed authenticate() test");
     47                 return;
     48             }
     49         }
     50 
     51         // 這個請求URI受到安全約束嗎?
     52         SecurityConstraint constraint = findConstraint(hrequest);
     53         if ((constraint == null) /*
     54                                      * && (!Constants.FORM_METHOD.equals(config.
     55                                      * getAuthMethod()))
     56                                      */ ) {
     57             if (debug >= 1)
     58                 log(" Not subject to any constraint(不受任何限制)");
     59             // 繼續執行下面的閥
     60             context.invokeNext(request, response);
     61             return;
     62         }
     63         if ((debug >= 1) && (constraint != null))
     64             log(" Subject to constraint " + constraint);
     65 
     66         // 確保受限制的資源不被web prox或瀏覽器緩存,因為緩存可能提供安全漏洞
     67         if (!(((HttpServletRequest) hrequest.getRequest()).isSecure())) {
     68             HttpServletResponse sresponse = (HttpServletResponse) response.getResponse();
     69             sresponse.setHeader("Pragma", "No-cache");
     70             sresponse.setHeader("Cache-Control", "no-cache");
     71             // 設置session過期
     72             sresponse.setDateHeader("Expires", 1);
     73         }
     74 
     75         // Enforce any user data constraint for this security constraint
     76         if (debug >= 1)
     77             log(" Calling checkUserData()");
     78         if (!checkUserData(hrequest, hresponse, constraint)) {
     79             if (debug >= 1)
     80                 log(" Failed checkUserData() test");
     81             // ASSERT: Authenticator already set the appropriate
     82             // HTTP status code, so we do not have to do anything special
     83             return;
     84         }
     85 
     86         // Authenticate based upon the specified login configuration
     87         if (constraint.getAuthConstraint()) {
     88             if (debug >= 1)
     89                 log(" Calling authenticate()");
     90             if (!authenticate(hrequest, hresponse, config)) {
     91                 if (debug >= 1)
     92                     log(" Failed authenticate() test");
     93                 // ASSERT: Authenticator already set the appropriate
     94                 // HTTP status code, so we do not have to do anything special
     95                 return;
     96             }
     97         }
     98 
     99         // Perform access control based on the specified role(s)
    100         if (constraint.getAuthConstraint()) {
    101             if (debug >= 1)
    102                 log(" Calling accessControl()");
    103             if (!accessControl(hrequest, hresponse, constraint)) {
    104                 if (debug >= 1)
    105                     log(" Failed accessControl() test");
    106                 // ASSERT: AccessControl method has already set the appropriate
    107                 // HTTP status code, so we do not have to do anything special
    108                 return;
    109             }
    110         }
    111 
    112         // Any and all specified constraints have been satisfied
    113         if (debug >= 1)
    114             log(" Successfully passed all security constraints");
    115         context.invokeNext(request, response);
    116 
    117     }

    隨意展示一個子類的authenticate方法的實現

  •  1 public boolean authenticate(HttpRequest request, HttpResponse response, LoginConfig config) throws IOException {
     2 
     3         // Have we already authenticated someone?
     4         Principal principal = ((HttpServletRequest) request.getRequest()).getUserPrincipal();
     5         if (principal != null) {
     6             if (debug >= 1)
     7                 log("Already authenticated '" + principal.getName() + "'");
     8             return (true);
     9         }
    10 
    11         // Validate any credentials already included with this request
    12         HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
    13         HttpServletResponse hres = (HttpServletResponse) response.getResponse();
    14         String authorization = request.getAuthorization();
    15         String username = parseUsername(authorization);
    16         String password = parsePassword(authorization);
    17         // 調用領域對象的驗證方法
    18         principal = context.getRealm().authenticate(username, password);
    19         if (principal != null) {
    20             register(request, response, principal, Constants.BASIC_METHOD, username, password);
    21             return (true);
    22         }
    23 
    24         // Send an "unauthorized" response and an appropriate challenge
    25         String realmName = config.getRealmName();
    26         if (realmName == null)
    27             realmName = hreq.getServerName() + ":" + hreq.getServerPort();
    28         // if (debug >= 1)
    29         // log("Challenging for realm '" + realmName + "'");
    30         hres.setHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
    31         // 授權失敗
    32         hres.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    33         // hres.flushBuffer();
    34         return (false);
    35 
    36     }

     

  • 來對用戶進行身份驗證,在其驗證方法中 會調用 與Context容器相關聯的領域對象 進行身份驗證

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM