How Tomcat works 之The Application 应用

来源:互联网 发布:成都和北京旅游 知乎 编辑:程序博客网 时间:2024/05/16 12:54

这里我想说 这里的翻译 基本上是按照原文翻译的,但是一些语句 我在理解的情况 尽可能的以简单的中文翻译出来。其实想到翻译 是因为看到国人的写的自己不懂 不理解 看了原版发现 竟然这么简单 因此下了决心 再加上近期在搞Tomcat这方面的开发 所以就顺便翻译了 希望各位看客 尽情拍砖 。最后还是想说 国人喜欢将简单地东西复杂化 比如数据结构 看了国文版的 简单蛋疼 原版的 一目了然。哎!也许是我语文功底不行。闲话少说。


这章的应用使用了几个与安全约束相关的Catalina类。这章与前一章都安排了SimpleWrapper类,SimplePipeline类和SimpleWrapperValue类。除此之外,SimpleContextConfig类与第九章的相似,但是这章新增了AuthentictorConfig方法,用来将一个BasicAUthentictor实例添加到StandardContext中。两章的应用都使用了两个Servlet:PrimitiveServlet和ModernServlet。


在第一个应用中有两个类,分别是Bootstrap1和SimpleRealm。第二个应用程序使用BootStrap2和SimpleUserDatabaseRealm。每个类将在下面的章节中解释。


ex10.pyrmont.core.SimpleContextConfig类


这一章节的SimpleContextConfig类与第九章的大致相似,由SimpleContext实例设置配置属性为true。只不过这章的SimpleContextConfig类的增加由lifeCycleEvent方法调用的AuthentictorConfig方法。这个方法实例化BasicAuthentictor类并作为一个Value添加到StandardContext实例的管道中。


package ex10.pyrmont.core;

import org.apache.catalina.Authenticator;

import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Pipeline;
import org.apache.catalina.Valve;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.deploy.LoginConfig;

public class SimpleContextConfig implements LifecycleListener {
   private Context context;
   public void lifecycleEvent(LifecycleEvent event) {
     if  (Lifecycle.START_EVENT.equals(event.getType())) {
         context = (Context) event.getLifecycle();
         authenticatorConfig();
         context.setConfigured(true);
      }
   }

   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?
       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;
       }
       // Has a Realm been configured for us to authenticate against?
       if (context.getRealm() == null) {
          return;
       }
       // 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) {
       }
   }
}


AuthentictorConfig方法开始检查是否有与context关联的安全约束。如果没有就返回null。代码如下:


 // Does this Context require an Authenticator?
       SecurityConstraint constraints[] = context.findConstraints();
      if ((constraints == null) || (constraints.length == 0))
          return;

如果有安全约束,AuthentictorConfig方法就检查context是否有一个LonginConfig对象。如果没有就创建一个。


  LoginConfig loginConfig = context.getLoginConfig();
      if (loginConfig == null) {
          loginConfig = new LoginConfig("NONE", null, null, null);
          context.setLoginConfig(loginConfig);
      } 


这个方法继续检查这个StandardContext对象的管道中的基本值或者另外其他的Value是否是一个管道。由于一个上下文只能有一个校验器,值中的一个将作为校验器返回。


 // 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;
      }

稍后它将检查一个realm是否与一个context关联。如果没有realm关联,如果没有,就没必要安装一个校验器,因为用户不能被再一次校验。


  // Has a Realm been configured for us to authenticate against?
      if (context.getRealm() == null) {
         return;
      }

在这点上,authentictorConfig方法将动态加载BasicAuthentictor类,并创建这个类的实例,将它作为一个Value添加到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) {           }
   }


ex10.pyrmont.realm.SimpleRealm类


这节演示了一个realm类如何工作的。这个类用于第一个应用程序中并包含了两个指定的用户名和密码。完整代码如下:


package ex10.pyrmont.realm;

import java.beans.PropertyChangeListener;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.catalina.Container;
import org.apache.catalina.Realm;
import org.apache.catalina.realm.GenericPrincipal;

public class SimpleRealm implements Realm {

public SimpleRealm() {
   createUserDatabase();
}
private Container container;

private ArrayList users = new ArrayList();
public Container getContainer() {
   return container;
}
public void setContainer(Container container) {
   this.container = container;
}
public String getInfo() {
   return "A simple Realm implementation";
}
public void addPropertyChangeListener(PropertyChangeListener
   listener) {      }
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());
}

public Principal authenticate(String username, byte[] credentials) {
   return null;
}
public Principal authenticate(String username, String digest,
   String nonce, String nc, String cnonce, String qop, String realm,
   String md5a2) {
   return null;
}
public Principal authenticate(X509Certificate certs[]) {
   return null;
}
public boolean hasRole(Principal principal, String role) {
   if ((principal == null) || (role == null) ||
       !(principal instanceof GenericPrincipal))
       return (false);
    GenericPrincipal gp = (GenericPrincipal) principal;

   if (! (gp.getRealm() == this))
       return (false);
   boolean result = gp.hasRole(role);
   return result;
}
public void removePropertyChangeListener(PropertyChangeListener
   listener) {      }
private User getUser(String username, String password) {
   Iterator iterator = users.iterator();
   while (iterator.hasNext()) {
       User user = (User) iterator.next();
       if (user.username.equals(username) &&
          user.password.equals(password))
          return user;
   }
   return null;
}

private void createUserDatabase() {
   User userl = new User("ken", "blackcomb");
   user1.addRole("manager");
   user1.addRole("programmer");
   User user2 = new User("cindy", "bamboo");
   user2.addRole("programmer");

   users.add(userl);
   users.add(user2);
}

class User {
   public User(String username, String password) {
       this.username = username;
       this.password = password;
   }
   public String username;
   public ArrayList roles = new ArrayList();
   public String password;
   public void addRole(String role) {
       roles.add (role);
   }
   public ArrayList getRoles() {
       return roles;
   }
}


下面分块分析:


SimpleRealm类实现了Realm接口。从构造器中,它调用了createUserDatabase方法,创建了两个用户。在内部,User是一个内部类。方法解释这里不细说,看了代码就都知道了。


 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);


这个类提供了四个重载校验方法之一实现。代码如下:


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());
   }


这个校验方法被一个校验器调用。如果传递的用户名和密码是一个无效用户就返回null值。否则,返回一个Principal对象表示用户。


ex10.pyrmont.realm.SimpleUserDatabaseRealm类


SimpleUserDatabaseRealm类代表一个更加复杂的realm。它不存储用户列表。相反,它读取conf目录下的tomcat-users.xml文件并加载内容到内存中。对应于这个list的校验器也创建了。在conf目录下,你将发现一份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="rolel" password="tomcat" roles="rolel"/>
   <user username="both" password="tomcat" roles="tomcat,rolel"/>
   <user username="admin" password="password" roles="admin,manager"/>
</tomcat-users>


SimpleUserDatabaseRealm类 代码如下:


package ex10.pyrmont.realm;
// modification of org.apache.catalina.realm.UserDatabaseRealm
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.catalina.Group;
import org.apache.catalina.Role;
import org.apache.catalina.User;
import org.apache.catalina.UserDatabase;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.users.MemoryUserDatabase;

public class SimpleUserDatabaseRealm extends RealmBase {
   protected UserDatabase database = null;
   protected static final String name = "SimpleUserDatabaseRealm";

   protected String resourceName = "UserDatabase";
   public Principal authenticate(String username, String credentials) {
      // Does a user with this username exist?
      User user = database.findUser(username);
      if (user == null) {
         return (null);
      }
      // Do the credentials specified by the user match?
      boolean validated = false;

if (hasMessageDigest()) {
       // Hex hashes should be compared case-insensitive
       validated =
         (digest(credentials).equalsIgnoreCase(user.getPassword()));
   }
    else {
       validated = (digest(credentials).equals(user.getPassword()));
   }
   if (!validated) {
       return null;
   }

   ArrayList combined = new ArrayList();
    Iterator roles = user.getRoles();
   while (roles.hasNext()) {
       Role role = (Role) roles.next();
       String rolename = role.getRolename();
       if (!combined.contains(rolename)) {
          combined.add(rolename);
       }
   }
    Iterator groups = user.getGroups();
   while (groups.hasNext()) {
       Group group = (Group) groups.next();
       roles = group.getRoles();
       while (roles.hasNext()) {
          Role role = (Role) roles.next();
          String rolename = role.getRolename();
          if (!combined, contains (rolename)) {
              combined.add(rolename);
          }
       }
   }
   return (new GenericPrincipal(this, user.getUsername(),
       user.getPassword(), combined));
}
protected Principal getPrincipal(String username) {
   return (null);
}
protected String getPassword(String username) {
   return null;
}
protected String getName() {
   return this.name;
 }
   public void createDatabase(String path) {

      database = new MemoryUserDatabase(name);
      ((MemoryUserDatabase) database).setPathname(path);
      try {
          database, open();
      }
      catch (Exception e)         {
      }
   }
}


在实例化SimpleUserDatabaseRealm类后,你必须调用它的createDatabase方法并将路径传递给包含用户列表的xml文档。这个方法实例化了MemoryUserDatabase类并执行读和解析xml文档。



ex10.pyrmont.startup.Bootstrap1 类


BootStrap1类启动了第一个应用程序。代码如下:


package ex10.pyrmont.startup;

import ex10.pyrmont.core.SimpleWrapper;
import ex10.pyrmont.core.SimpleContextConfig;
import ex10.pyrmont.realm.SimpleRealm;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap1 {
   public static void main(String[] args) {
     System.setProperty("catalina.base",
        System.getProperty("user.dir"));
     Connector connector = new HttpConnector();
     Wrapper wrapper1 = new SimpleWrapper();
     wrapper1.setName("Primitive");
     wrapper1.setServletClass("PrimitiveServlet");
     Wrapper wrapper2 = new SimpleWrapper();
     wrapper2.setName("Modern");

     wrapper2.setServletClass("ModernServlet");

     Context context = new StandardContext();
     // StandardContext's start method adds a default mapper
     context.setPath("/myApp");
     context.setDocBase("myApp");
     LifecycleListener listener = new SimpleContextConfig();
     ((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();
     securityCollection.addPattern("/");
     securityCollection.addMethod("GET");

     SecurityConstraint constraint = new SecurityConstraint();
     constraint.addCollection(securityCollection);

constraint.addAuthRole("manager");
      LoginConfig loginConfig = new LoginConfig();
      loginConfig.setRealmName("Simple Realm");
      // add realm
      Realm realm = new SimpleRealm();

      context.setRealm(realm);
      context.addConstraint(constraint);
      context.setLoginConfig(loginConfig);

      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();
      }
   }
}


这个类的主要方法是创建两个SimpleWrapper对象并调用了setName和setServletClass方法。对于第一个SimpleWrapper对象,你将Primitive方法传递给它的setName方法并将PrimitiveServlet传递给它的setServletClass方法。第二个SimpleWrapper对象取得modern和ModernServlet。


稍后又创建了一个StandardContext对象,设置它的路径和文档base,并增加了SimpleContextConfig类型的监听器。之后在StandardContext对象中安装了一个BasicAuthentictor。下一步,它为StandardContext添加了一个加载器和两个Servlet映射。大多数代码与第九章相似,新的代码如下:


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


main方法创建了一个SecurityCollection对象并调用了它的addPattern方法和addMethod方法。addPattern方法指明了安全约束将适用于哪个URL。addMethod方法增加一个方法将由受制于这个约束。addMethod方法获取GET,GET的HTTP请求将受制于这个约束。


下一步,main方法实例化了SecurityConstraint类并将其添加到集合中。它也设置角色能够访问受限资源。通过传递Manager,Manager角色的用户可以看到这些资源。注意SimpleRealm类仅有一个用户有Manager角色,ken。他的密码是blackcomb。


                SecurityConstraint constraint = new SecurityConstraint();
                constraint.addCollection(securityCollection);
                constraint.addAuthRole("manager");



下一步,main方法创建了一个LoginConfig对象和SimpleRealm对象。


              LoginConfig loginConfig = new LoginConfig();
              loginConfig.setRealmName("Simple Realm");
              // add realm
              Realm realm = new SimpleRealm(); 



稍后,它使realm,constraint和loginConfig对象与StandardContext对象关联。


                  context.setRealm(realm);
                  context.addConstraint(constraint);
                  context.setLoginConfig(loginConfig);



下一步,启动context。这里已经在上一章节中讲过。


结果,对PrimitiveServlet和ModernServlet的访问受限。如果用户访问上述的Servlet,他必须使用basic校验器。只有在输入正确的用户名和密码,用户才被允许访问。


ex10.pyrmont.startup.BootStrap2类


这个类开启了第二个应用,与第一个应用相似,除了它使用了SimpleUserDatabase的实例作为与StandardContext相关的realm。为了访问PrimitiveServlet和ModernServlet,用户名和密码必须分别对应正确的用户名和密码。


package ex10.pyrmont.startup;

import ex10.pyrmont.core.SimpleWrapper;
import ex10.pyrmont.core.SimpleContextConfig;
import ex10.pyrmont.realm.SimpleUserDatabaseRealm;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Realm;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap2 {
   public static void main(String[] args) {
      System.setProperty("catalina.base",
         System.getProperty("user.dir"));
      Connector connector = new HttpConnector();
      Wrapper wrapper1 = new SimpleWrapper();
      wrapper1.setName("Primitive");
      wrapper1.setServletClass("PrimitiveServlet");
      Wrapper wrapper2 = new SimpleWrapper();
      wrapper2.setName("Modern");
      wrapper2.setServletClass("ModernServlet");

      Context context = new StandardContext();

      // StandardContext's start method adds a default mapper
      context.setPath("/myApp");
      context.setDocBase("myApp");
      LifecycleListener listener = new SimpleContextConfig();
      ((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();
securityCollection.addPattern("/");
securityCollection.addMethodf"GET");

SecurityConstraint constraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
LoginConfig loginConfig = new LoginConfig();
loginConfig.setRealmName("Simple User Database Realm");
// add realm
Realm realm = new SimpleUserDatabaseRealm();
((SimpleUserDatabaseRealm) realm).
   createDatabase("conf/tomcat-users.xml");
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);

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();
      }
   }
}


总结


安全在Servlet编程中是一个重要的主题。Servlet使用Security满足安全需求。,与Security相关的对象,比如principle,roles,安全约束,登陆配置等等。在这一章节中学会了Servlet容器如何解决这个问题。





0 0
原创粉丝点击