【0】README
0.0)本文部分描述转自“深入剖析tomcat”,旨在学习 tomcat(10)安全性 的基本知识;
0.1)servlet技术支持通过配置部署描述器(web.xml)文件来对这些内容进行访问控制;(干货——web.xml又名 配置部署描述器)
0.2)servlet容器是通过一个名为验证器的阀来支持安全限制的,该阀会被添加到Context的管道中,并且会先于 Wrapper阀的调用;(干货——引入了验证器阀)
0.3)调用验证器阀:
case1)用户输入了正确的username 和 pwd:验证器阀会调用后续的阀;
case2)用户输入错误:验证器阀会返回,不会调用后续的阀;
0.4)验证器阀会调用Context容器的领域对象的 authenticate()方法,传入用户输入的username 和 pwd,来对用户进行身份验证;(干货——引入了领域对象)

【1】领域(Realm,用来对用户进行身份验证的组件)
1)intro to 领域对象:该对象用来对用户进行身份验证的组件;
1.1)领域对象通常与一个Context容器相关联(setRealm方法来将领域对象与Context容器相关联);
1.2)领域对象如何验证用户身份呢?它保存了所有有效用户的username 和 pwd对,或者它会访问存储这些dta的存储器;(在tomcat中,有效info 存储在tomcat-user.xml文件中,现在你知道tomcat-user.xml文件的作用是什么了吧)
private void createUserDatabase() {// SimpleRealm.createUserDatabase() 创建了users 列表;
    User user1 = new User("ken", "blackcomb");
    user1.addRole("manager");
    user1.addRole("programmer");
    User user2 = new User("cindy", "bamboo");
    user2.addRole("programmer");

    users.add(user1);
    users.add(user2);
  }
2)领域对象:是 org.apache.catalina.Realm 接口的实例,该接口的 authenticate方法最为重要,方法签名如下:
public Principal authenticate (String username, String credentials); // generally used
public Principal authenticate (String username, byte[] credentials);
public Principal authenticate (String username, String digest, String nonce, String nc, String cnonce, String qop, String realm, String md5a2);
public Principal authenticate (X509Certificate cents[]);;
3)在catalina中, Realm接口的基本实现形式是org.apache.catalina.realm.RealmBase类;其UML类图如下:
3.1)default case:会使用 MemoryRealm 类的实例作为验证用的领域对象;(干货——MemoryRealm 类的实例作
为验证默认用的领域对象)
3.2)当第一次调用 MemoryRealm实例时,它会读取 tomcat-user.xml 文档的内容;
Attention)在catalina中, 验证器阀会调用附加到其中的领域对象的 authenticate()方法来验证用户身份;

【2】GenericPrincipal类(实现接口 java.security.Principal)
public class GenericPrincipal implements Principal { //org.apache.catalina.realm.GenericPrincipal,代表一个用户(封装了username,password,role,realm)
    public GenericPrincipal(Realm realm, String name, String password) {
        this(realm, name, password, null);
    }
    public GenericPrincipal(Realm realm, String name, String password,
                            List roles) {

        super();
        this.realm = realm;
        this.name = name;
        this.password = password;
        if (roles != null) {
            this.roles = new String[roles.size()];
            this.roles = (String[]) roles.toArray(this.roles);
            if (this.roles.length > 0)
                Arrays.sort(this.roles);
        }
    }

    protected String name = null;

    public String getName() {
        return (this.name);
    }
    protected String password = null; 

    public String getPassword() {
        return (this.password);
    }
    protected Realm realm = null; 

    public Realm getRealm() {
        return (this.realm);
    }
    protected String roles[] = new String[0]; 

    public String[] getRoles() {
        return (this.roles);
    }
    public boolean hasRole(String role) {  // highlight line.
        if (role == null)
            return (false);
        return (Arrays.binarySearch(roles, role) >= 0);
    }
    public String toString() {
        StringBuffer sb = new StringBuffer("GenericPrincipal[");
        sb.append(this.name);
        sb.append("]");
        return (sb.toString());
    }
}
1)intro to GenericPrincipal:
1.1)GenericPrincipal实例:必须始终与一个领域对象相关联;
1.2)GenericPrincipal实例:必须有一个用户名和密码对;且,该用户名和密码对所对应的角色列表是可选的;
1.3)调用hasRole()方法:传入1个字符串形式的角色名来检查该主体对象是否拥有该指定角色;

【3】LoginConfig类(org.apache.catalina.deploy.LoginConfig)
1)intro to LoginConfig:登录配置是 LoginConfig类的实例,其中包含一个领域对象的名字,其实例封装了领域对象名和所要使用的身份验证方法;(getRealmName()方法用来获取领域对象的名字);
2)getAuthName()方法获取身份验证方法的名字:名字范围是,BASIC, DIGEST, FORM 或 CLIENT-CERT;
3)实际部署中:tomcat在启动时需要读取 web.xml 文件的内容;
step1)如果web.xml 文件包含 login-config 元素的配置,则tomcat会创建一个 LoginConfig对象,并设置其相应属性;(干货——tomcat创建一个 LoginConfig对象的条件)
step2)验证器阀会调用 LoginConfig.getRealmName() 获取领域对象名,并将领域对象名发送到 browser,显示在登录对话框中;
case2.1)如果getReamlName()方法的返回值是null,则会将服务器名和相应端口发送给 browser;
看个荔枝)下图展示了 XP 系统中使用 IE6 进行基本身份验证的登录对话框
public final class LoginConfig { //org.apache.catalina.deploy.LoginConfig
    
    public LoginConfig() {
        super();
    }    
    public LoginConfig(String authMethod, String realmName,
                       String loginPage, String errorPage) {
        super();
        setAuthMethod(authMethod);
        setRealmName(realmName);
        setLoginPage(loginPage);
        setErrorPage(errorPage);
    }
    // ------------------------------------------------------------- Properties   
    private String authMethod = null;
    public String getAuthMethod() {
        return (this.authMethod);
    }
    public void setAuthMethod(String authMethod) {
        this.authMethod = authMethod;
    }
  
    private String errorPage = null;
    public String getErrorPage() {
        return (this.errorPage);
    }
    public void setErrorPage(String errorPage) {        
        this.errorPage = RequestUtil.URLDecode(errorPage);
    }    
    private String loginPage = null;
    public String getLoginPage() {
        return (this.loginPage);
    }
    public void setLoginPage(String loginPage) {       
        this.loginPage = RequestUtil.URLDecode(loginPage);
    }    
    private String realmName = null;
    public String getRealmName() {
        return (this.realmName);
    }
    public void setRealmName(String realmName) {
        this.realmName = realmName;
    }   
    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());
    }
}
【4】Authenticator接口(org.apache.catalina.Authenticator)
1)intro to Authenticator:验证器接口只是起到了标记作用,这样其他组件就可以使用 instanceof 来检查某个组件是否是一个验证器;(干货——验证器接口只是起到了标记作用)
2)org.apache.catalina.Authenticator的UML类图如下:

对上图的分析(Analysis):
A1)BasicAuthenticator:用来支持基本的身份验证;
A2)FormAuthenticator:提供了基于表单的身份验证;
A3)DigestAuthenticator:提供了基于信息摘要的身份验证;
A4)SSLAuthenticator:用于对SSL 进行身份验证;
A5)当tomcat 用户没有指定验证方法名时,NonLoginAuthenticator类用于对来访者的身份进行验证。NonLoginAuthenticator类实现的验证器只会检查安全限制,而不会涉及用户身份的验证;
Attention)
A1)验证器的重要工作:是对用户进行身份验证;(干货——验证器的重要工作是对用户进行身份验证)
A2)当看到 AuthenticatorBase.invoke() 方法调用 authenticate() 抽象方法时:后者的实现依赖于子类;(而authenticate()方法会使用基本身份验证来验证用户的身份信息);
public class BasicAuthenticator 
    extends AuthenticatorBase {  <span style="font-family: SimSun;">//org.apache.catalina.authenticator.BasicAuthenticator,这里仅以BasicAuthenticator 为例po出 source code.</span>
    // ----------------------------------------------------- Instance Variables    
    protected static final Base64 base64Helper = new Base64();    
    protected static final String info =
        "org.apache.catalina.authenticator.BasicAuthenticator/1.0";   
    public String getInfo() {
        return (this.info);
    }
    
    public boolean authenticate(HttpRequest request,
                                HttpResponse response,
                                LoginConfig config)
        throws IOException {
        // Have we already authenticated someone?
        Principal principal =
            ((HttpServletRequest) request.getRequest()).getUserPrincipal();
        String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
        if (principal != null) {
            if (debug >= 1)
                log("Already authenticated '" + principal.getName() + "'");
            // Associate the session with any existing SSO session
            if (ssoId != null)
                associate(ssoId, getSession(request, true));
            return (true);
        }

        // Is there an SSO session against which we can try to reauthenticate?
        if (ssoId != null) {
            if (debug >= 1)
                log("SSO Id " + ssoId + " set; attempting reauthentication");           
            if (reauthenticateFromSSO(ssoId, request))
                return true;
        }

        // Validate any credentials already included with this request
        HttpServletRequest hreq =
            (HttpServletRequest) request.getRequest();
        HttpServletResponse hres =
            (HttpServletResponse) response.getResponse();
        String authorization = request.getAuthorization();
        String username = parseUsername(authorization);
        String password = parsePassword(authorization);
        principal = context.getRealm().authenticate(username, password);
        if (principal != null) {
            register(request, response, principal, Constants.BASIC_METHOD,
                     username, password);
            return (true);
        }

        // Send an "unauthorized" response and an appropriate challenge
        String realmName = config.getRealmName();
        if (realmName == null)
            realmName = hreq.getServerName() + ":" + hreq.getServerPort();    
        hres.setHeader("WWW-Authenticate",
                       "Basic realm=\"" + realmName + "\"");
        hres.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        //      hres.flushBuffer();
        return (false);

    }
    // ------------------------------------------------------ Protected Methods
    protected String parseUsername(String authorization) {

        if (authorization == null)
            return (null);
        if (!authorization.toLowerCase().startsWith("basic "))
            return (null);
        authorization = authorization.substring(6).trim();

        // Decode and parse the authorization credentials
        String unencoded =
          new String(base64Helper.decode(authorization.getBytes()));
        int colon = unencoded.indexOf(':');
        if (colon < 0)
            return (null);
        String username = unencoded.substring(0, colon);
        //        String password = unencoded.substring(colon + 1).trim();
        return (username);
    }
    protected String parsePassword(String authorization) {
        if (authorization == null)
            return (null);
        if (!authorization.startsWith("Basic "))
            return (null);
        authorization = authorization.substring(6).trim();

        // Decode and parse the authorization credentials
        String unencoded =
          new String(base64Helper.decode(authorization.getBytes()));
        int colon = unencoded.indexOf(':');
        if (colon < 0)
            return (null);
        //        String username = unencoded.substring(0, colon).trim();
        String password = unencoded.substring(colon + 1);
        return (password);
    }
}
【5】安装验证器阀
1)一个Context实例:只能有一个 LoginConfig实例和利用一个验证类的实现;
2)也就是说:在部署描述器(tomcat-user.xml)中,login-config元素只出现一次;login-config元素包含一个auth-method 元素来指定身份验证方法;
3)使用AuthenticatorBase类的哪个子类作为Context实例中的验证器阀:依赖于 部署描述器中 auth-method元素;
4)下表给出了 auth-method 元素的值和对应的验证器的类名:

对上表的分析(Analysis):
A1)若没有设置 auth-method元素:则 LoginConfig 对象 的 atuh-method属性的值默认为 NONE,使用 NonLoginAuthenticator 进行安全验证;
A2)由于使用的验证器类是在运行时才确定的,故该类是动态载入的;
A3)StandardContext容器使用 org.apahce.catalina.startup.ContextConfig类来对 StandardContext 实例的属性进行设置:这些设置包括 实例化一个验证器类,并将该实例与Context实例相关联;

【6】应用程序
【6.1】SimpleContextConfig类
1)intro:public class SimpleContextConfig implements LifecycleListener ,仅仅是个监听器而已,不要惊慌;
2)SimpleContextConfig.authenticatorConfig()方法:该方法实例化BasicAuthenticator类,并将其作为阀添加到 StandardContext实例的管道中;(干货——注意其实例化BasicAuthenticator类的技巧
// Identify the class name of the Valve we should configure
    String authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";
    // Instantiate and install an Authenticator of the requested class
    Valve authenticator = null;
    try {
      Class authenticatorClass = Class.forName(authenticatorName);
      authenticator = (Valve) authenticatorClass.newInstance();
      ((StandardContext) context).addValve(authenticator);
      System.out.println("Added authenticator valve to Context");
    }
    catch (Throwable t) {
    }
3)下面对SimpleContextConfig.authenticatorConfig()方法的调用过程 进行 detailed analysis:
step1)先检查在相关联的Context容器是否有安全限制,若没有直接返回,而不会安装验证器;
// Does this Context require an Authenticator?
    SecurityConstraint constraints[] = context.findConstraints();
    if ((constraints == null) || (constraints.length == 0))
      return;
step2)若当前Context容器有一个或多个安全限制,authenticatorConfig() 方法会检查该Context实例是否有 LoginConfig对象。若没有,则它会创建一个新的 LoginConfig实例:
LoginConfig loginConfig = context.getLoginConfig();
    if (loginConfig == null) {
      loginConfig = new LoginConfig("NONE", null, null, null);
      context.setLoginConfig(loginConfig);
    }
step3)检查管道中的基础阀或附加阀是否是验证器。因为一个Context实例只能有一个验证器,所以当发现某个阀是验证器后,直接返回:
// Has an authenticator been configured already?
    Pipeline pipeline = ((StandardContext) 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 { // no Pipeline, cannot install authenticator valve
      return;
    }
step4)查找当前Context实例是否有与之关联的领域对象(Realm)。如果没有领域对象,就不需要安装验证器了,因为用户是无法通过身份验证的;
// Has a Realm been configured for us to authenticate against?
    if (context.getRealm() == null) {
      return;
    }
step5)若找到了领域对象,则会动态载入 BasicAuthenticator类,创建该类的一个实例,并将其作为阀添加到 StandardContext实例中;
// Identify the class name of the Valve we should configure
    String authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";
    // Instantiate and install an Authenticator of the requested class
    Valve authenticator = null;
    try {
      Class authenticatorClass = Class.forName(authenticatorName);
      authenticator = (Valve) authenticatorClass.newInstance();
      ((StandardContext) context).addValve(authenticator);
      System.out.println("Added authenticator valve to Context");
    }
    catch (Throwable t) {
    }
  }
【6.2】SimpleRealm类(简单领域对象,领域对象是用来对用户身份验证的组件)
1)SimpleRealm类实现了Realm接口;
2)在构造函数中,它调用了createUserDatabase()方法创建两个用户,并将这两个用户添加到users中;(干货——创建用户,并设置角色,功能同tomcat-users.xml 的内容)
public class SimpleRealm implements Realm {
  public SimpleRealm() {
    createUserDatabase(); //highlight line.
  }
 private void createUserDatabase() { // there are 2 roles.
    User user1 = new User("ken", "blackcomb");
    user1.addRole("manager"); // manager role.
    user1.addRole("programmer"); // programmer role.
    User user2 = new User("cindy", "bamboo");
    user2.addRole("programmer");
    users.add(user1);
    users.add(user2);
    // private ArrayList users = new ArrayList();
  }
}
3)再看其 authenticate() 验证方法:该方法由验证器调用,若用户提供的用户名或密码无效,则返回null,否则返回一个代表该用户的 Principal对象;
public Principal authenticate(String username, String credentials) {
    System.out.println("SimpleRealm.authenticate()");
    if (username==null || credentials==null)
      return null;
    User user = getUser(username, credentials);
    if (user==null)
      return null;
    return new GenericPrincipal(this, user.username, user.password, user.getRoles()); // highlight line.
  }
public class GenericPrincipal implements Principal { // 该类封装了用户的一些信息,如username,pass,role,realm等info;
    public GenericPrincipal(Realm realm, String name, String password) { 
        this(realm, name, password, null);
    }    
    public GenericPrincipal(Realm realm, String name, String password,
                            List roles) {
        super();
        this.realm = realm;
        this.name = name;
        this.password = password;
        if (roles != null) {
            this.roles = new String[roles.size()];
            this.roles = (String[]) roles.toArray(this.roles);
            if (this.roles.length > 0)
                Arrays.sort(this.roles);
        }
    }
    protected String name = null;
    public String getName() {
        return (this.name);
    }
    protected String password = null;
    public String getPassword() {
        return (this.password);
    }
    protected Realm realm = null;
    public Realm getRealm() {
        return (this.realm);
    }
    protected String roles[] = new String[0];
    public String[] getRoles() {
        return (this.roles);
    }       
    public boolean hasRole(String role) {
        if (role == null)
            return (false);
        return (Arrays.binarySearch(roles, role) >= 0);
    }    
    public String toString() {
        StringBuffer sb = new StringBuffer("GenericPrincipal[");
        sb.append(this.name);
        sb.append("]");
        return (sb.toString());
    } 
}
【6.3】SimpleUserDatabaseRealm(它是SimpleRealm的变体类)
1)intro:SimpleUserDatabaseRealm类表示一个复杂一点的领域对象,它并不将用户列表存储到对象自身中。相反,它会读取conf 目录下的 tomcat-users.xml文件,将内容载入内存,然后依据该列表进行身份验证。(干货——读取conf 目录下的 tomcat-users.xml文件,将内容载入内存,然后依据该列表进行身份验证
// tomcat-users.xml 的源码如下:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="admin" password="admin" roles="admin,manager"/>
</tomcat-users>
2)在实例化SimpleUserDatabaseRealm类后, 必须调用它的 createDatabase()方法,并向包含用户列表的xml 文档传递路径。
// 以下代码是 Bootstrap2.java 中的源码
 Realm realm = new SimpleUserDatabaseRealm();
    String filedir = new File(System.getProperty("user.dir")).getParent() + File.separator +
      "conf" + File.separator + "tomcat-users.xml";
    ((SimpleUserDatabaseRealm) realm).createDatabase(filedir); // highlight line.
public void createDatabase(String path) { //  SimpleUserDatabaseRealm.createDatabase().
    database = new MemoryUserDatabase(name);
    ((MemoryUserDatabase) database).setPathname(path);
    try {
      database.open(); // highlight line.
    }
    catch (Exception e)  {
    }
  }
public void open() throws Exception { // org.apache.catalina.users.MemoryUserDatabase.open().
        synchronized (groups) {
            synchronized (users) {
                // Erase any previous groups and users
                users.clear();
                groups.clear();
                roles.clear();
                // Construct a reader for the XML input file (if it exists)
                File file = new File(pathname);
                if (!file.isAbsolute()) {
                    file = new File(System.getProperty("catalina.base"),
                                    pathname);
                }
                if (!file.exists()) {
                    return;
                }
                FileInputStream fis = new FileInputStream(file);
                // Construct a digester to read the XML input file
                Digester digester = new Digester();
                digester.addFactoryCreate
                    ("tomcat-users/group",
                     new MemoryGroupCreationFactory(this));
                digester.addFactoryCreate
                    ("tomcat-users/role",
                     new MemoryRoleCreationFactory(this));
                digester.addFactoryCreate
                    ("tomcat-users/user",
                     new MemoryUserCreationFactory(this));

                // Parse the XML input file to load this database
                try {
                    digester.parse(fis);
                    fis.close();
                } catch (Exception e) {
                    try {
                        fis.close();
                    } catch (Throwable t) {
                        ;
                    }
                    throw e;
                }             }        }     }
【6.4】Bootstrap1.java(应用程序1)
1)Bootstrap1.java 的源代码
public final class Bootstrap1 {
  public static void main(String[] args) {
  //invoke: http://localhost:8080/Modern or  http://localhost:8080/Primitive
    System.setProperty("catalina.base", System.getProperty("user.dir"));
    System.out.println("user.dir = " + System.getProperty("user.dir"));
    
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("servlet.PrimitiveServlet");
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("servlet.ModernServlet");

    Context context = new StandardContext(); // highlight line.
    // StandardContext's start method adds a default mapper
    context.setPath("/myApp");
    context.setDocBase("myApp");
    LifecycleListener listener = new SimpleContextConfig();// highlight line.
    ((Lifecycle) context).addLifecycleListener(listener);

    context.addChild(wrapper1);
    context.addChild(wrapper2);
    // for simplicity, we don't add a valve, but you can add
    // valves to context or wrapper just as you did in Chapter 6

    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    // add ContextConfig. This listener is important because it configures
    // StandardContext (sets configured to true), otherwise StandardContext
    // won't start

    // add constraint
    SecurityCollection securityCollection = new SecurityCollection();// highlight line.
    securityCollection.addPattern("/");// highlight line.
    securityCollection.addMethod("GET");// highlight line.

    SecurityConstraint constraint = new SecurityConstraint();// highlight line.
    constraint.addCollection(securityCollection);// highlight line.
    constraint.addAuthRole("manager");// highlight line.
    LoginConfig loginConfig = new LoginConfig();// highlight line.
    loginConfig.setRealmName("Simple Realm");
    // add realm
    Realm realm = new SimpleRealm();// highlight line.

    context.setRealm(realm);// highlight line.
    context.addConstraint(constraint);// highlight line.
    context.setLoginConfig(loginConfig);// highlight line.

    connector.setContainer(context);

 // add a Manager
    Manager manager = new StandardManager();
    context.setManager(manager);
    
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) context).start();

      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) context).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}
对以上代码的分析(Analysis)
A1)创建StandardContext对象:设置其path属性和 documentBase属性,并添加一个 SimpleContextConfig 类的监听器。该监听器会把一个 BasicAuthenticator 对象安装到 StandardContext 对象中;
A2)创建SecurityColleciton对象:并调用其addPattern和 addMethod方法,addPattern方法指定某个url 要遵循哪个安全限制, 而addMethod方法会指定该安全限制要使用哪种验证方法;在addMethod()方法中设置为GET, 则使用GET 方法提交的http 请求会遵循安全限制;
A3)创建 SecurityManager对象:并将其添加到 安全限制集合中,且还设置了哪种角色可以访问这些受限资源。
A4)创建LoginConfig对象 和 SimpleRealm对象;
A5)将领域对象,安全限制对象,登录配置对象 与 StandardContext实例相关联;
A6)接着,启动Context实例。。。。。。。
Supplement-补充)
S1)本文还是给出了如何验证用户合法性的调用过程




S2)结合S1中的第一张图,本文关联性地给出了 server处理http 客户端请求的调用过程


2)打印结果
<pre name="code" class="java">E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomc
t.chapter10.startup.Bootstrap1
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
Starting Wrapper Primitive
Starting Wrapper Modern
Added authenticator valve to Context
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException 
// 这是从文件中加载 session对象到内存,由于没有相关文件,所以加载失败,抛出异常,但这不会影响我们访问servlet,大家不要惊慌;
java.io.EOFException
        at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)
        at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)
        at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
        at java.io.ObjectInputStream.<init>(Unknown Source)
        at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)
        at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)
        at com.tomcat.chapter10.startup.Bootstrap1.main(Bootstrap1.java:84)
StandardManager[/myApp]: Exception loading sessions from persistent storage
java.io.EOFException
        at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)
        at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)
        at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
        at java.io.ObjectInputStream.<init>(Unknown Source)
        at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)
        at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)
        at com.tomcat.chapter10.startup.Bootstrap1.main(Bootstrap1.java:84)
SimpleRealm.authenticate()
SimpleRealm.authenticate()
ModernServlet -- init
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
init
SimpleRealm.authenticate()
from service
from service

【6.5】Bootstrap2.java(应用程序2)
1)Bootstrap2的源程序和 Bootstrap1.java 的源程序差不多;
public final class Bootstrap2 {
  public static void main(String[] args) {

  //invoke: http://localhost:8080/Modern or  http://localhost:8080/Primitive

    System.setProperty("catalina.base", System.getProperty("user.dir"));
    Connector connector = new HttpConnector();
    Wrapper wrapper1 = new SimpleWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("servlet.PrimitiveServlet");
    Wrapper wrapper2 = new SimpleWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("servlet.ModernServlet");

    Context context = new StandardContext(); // highlight line.
    // StandardContext's start method adds a default mapper
    context.setPath("/myApp");
    context.setDocBase("myApp");
    LifecycleListener listener = new SimpleContextConfig();// highlight line.
    ((Lifecycle) context).addLifecycleListener(listener);

    context.addChild(wrapper1);
    context.addChild(wrapper2);
    // for simplicity, we don't add a valve, but you can add
    // valves to context or wrapper just as you did in Chapter 6

    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");
    // add ContextConfig. This listener is important because it configures
    // StandardContext (sets configured to true), otherwise StandardContext
    // won't start

    // add constraint
    SecurityCollection securityCollection = new SecurityCollection();// highlight line.
    securityCollection.addPattern("/");// highlight line.
    securityCollection.addMethod("GET");// highlight line.

    SecurityConstraint constraint = new SecurityConstraint();// highlight line.
    constraint.addCollection(securityCollection);// highlight line.
    constraint.addAuthRole("manager");// highlight line.
    constraint.addAuthRole("tomcat");// highlight line.注意这里的角色,必要要和tomcat-users.xml 中的users 列表相对应,如果要设置某个用户有权限访问servlet资源,则需要添加其所属的角色;
    LoginConfig loginConfig = new LoginConfig();// highlight line.
    loginConfig.setRealmName("Simple User Database Realm");
    // add realm
    Realm realm = new SimpleUserDatabaseRealm();// highlight line.
    String filedir = new File(System.getProperty("user.dir")).getParent() + File.separator + 
    	"conf" + File.separator + "tomcat-users.xml"; 
    ((SimpleUserDatabaseRealm) realm).createDatabase(filedir); // highlight line.设置tomcat-users.xml 的文件路径 以载入其内容到内存进行身份验证工作;
    
    context.setRealm(realm);// highlight line.
    context.addConstraint(constraint);// highlight line.
    context.setLoginConfig(loginConfig);// highlight line.

    connector.setContainer(context);

    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) context).start();

      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) context).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }}
// tomcat-users.xml 的源码如下:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="admin" password="admin" roles="admin,manager"/>
</tomcat-users>
2)它们唯一的差别在于以不同的方式获取用户列表:Bootstrap1.java 是在对象中创建用户列表保存在 ArrayList中;而Bootstrap2.java 通过读取conf目录下的 tomcat-users.xml文件,将其内容载入到内存中;(干货——Bootstrap1.java 和 Bootstrap2.java的唯一差别)(干货——你现在知道 tomcat-users.xml 的作用了,以及如何利用它进行安全性访问的限制)
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common.
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;lib\commons-digester.jar;lib\commons-logging.jar;E:\bench-cluster\cloud
-data-preprocess\HowTomcatWorks\webroot com/tomcat/chapter10/startup/Bootstrap2
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
Starting Wrapper Primitive
Starting Wrapper Modern
Added authenticator valve to Context
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException // 这是从文件中加载 session对象到内存,由于没有相关文件,所以加载失败,抛出异常,但这不会影响我们访问servlet,大家不要惊慌;
java.io.EOFException
        at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)
        at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)
        at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
        at java.io.ObjectInputStream.<init>(Unknown Source)
        at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)
        at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)
        at com.tomcat.chapter10.startup.Bootstrap2.main(Bootstrap2.java:84)
StandardManager[/myApp]: Exception loading sessions from persistent storage  
java.io.EOFException  
        at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)
        at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)
        at java.io.ObjectInputStream.readStreamHeader(Unknown Source)
        at java.io.ObjectInputStream.<init>(Unknown Source)
        at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)
        at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)
        at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)
        at com.tomcat.chapter10.startup.Bootstrap2.main(Bootstrap2.java:84)
ModernServlet -- init
3)打印结果


Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐