• 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容器相关联的领域对象 进行身份验证
  • 相关阅读:
    vue 之 数据传递(子传父,父传子,非父子通信<事件总线>,父取子<ref,$refs>,插槽,provide和inject数据传递)
    vue 之 $ref 和 $refs
    node 之 身份认证(cookie,session,token<jwt>)
    vue 之 事件总线(订阅者模式,非父子间的数据传递)
    node 之 模块汇总
    node 之 web开发模式
    node 之 路由(待完善)
    node 之 浏览器跨域问题(待完善)
    Apache配置URL重定向
    自定义去除博客园底部的广告和链接推荐
  • 原文地址:https://www.cnblogs.com/ChenD/p/Tomcat_Realm.html
Copyright © 2020-2023  润新知