How tomcat works——10 安全性

概述

一些web 应用内容是受限的,只有在有特定权限的用户输入正确的用户名和密码后才能访问。Servlet 通过配置部署文件 web.xml 来对安全性提供技术支持。本章将介绍 container 如何支持安全性控制。

servlet 容器通过一个称为 authenticator 的阀门(valve)来支持安全认证。当 container 启动时,authenticator 被添加到container 的 pipeline中。如果忘记了pipeline工作机制,请阅读第6章。

在 wrapper阀门被调用之前,会先调用 authenticator阀门,用来对用户进行认证。若用户输入了正确的用户名密码,则 authenticator 会调用下一个阀门,否则会直接返回,不再继续执行剩余的阀门。由于验证失败,用户并不能看到请求的 servlet。

在用户验证时, authenticator 阀门调用的是上下文域(realm)内的authenticate()方法,将用户名和密码传递给它。领域(Realm)可以访问有效用户名和密码的集合。

本章会先介绍一些 servlet 编程中与安全相关的对象(realm,role,principal 等),然后通过一个应用程序来演示如何为servlet 添加基本的认证功能。

注意:这里假设大家已经熟悉 servlet 编程安全性的相关概念,包括: principals, roles, realms, login configuration等。如果对这些概念还不清楚可以阅读《Java for the Web with Senlets, JSP,and EJB》或者其它相关书籍。

10.1 领域(Realm)

域(Realm)是用于进行用户验证的一个组件。它验证一用户名&密码是否是合法。一个域跟一个上下文容器相联系,并且一个容器只有一个域。可以使用容器的 setRealm()方法来建立它们之间的联系。

一个域是如何验证一个用户的合法性的昵?一个域拥有所有的合法用户的用户名和密码或
者是可以访问到存储它们的地方。至于它们存放在哪里则取决于域的具体实现。在 Tomcat默认实现里,合法用户被存储在 tomcat-users.xml 文件里。但是,也可以使用域的其它实现,如关系数据库。

在 Catalina 中,一个域用接口 org.apache.catalina.Realm 表示。该接口最重要的方法是4个 重载authenticate()方法:

public Principal authenticate(String username, String credentials);
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 certs[]);

第一个方法最常用。Realm 接口还有一个 getRole()方法,签名如下:

public boolean hasRole(Principal principal, String role);

另外,域还有 getContainer() 和 setContainer() 方法用于建立域与容器关联。

抽象类 org.apache.catalina.realm.RealmBase是一个域的基本实现。org.apache.catalina.realm 包中还提供了其它继承了 RealmBase的一些实现类,如:JDBCRealm, JNDIRealm, MemoryRealm和 UserDatabaseRealm。默认情况下使用的域是 MemoryRealm。当MemoryRealm首次启动时,它会读取tomcat-users.xml文件。在本章Demo中,将会自定义一简单的Realm来存储用户信息。

注意:在 Catalina 中,authenticator阀门调用相关域的 authenticate()方法来验证一个用户。

10.2 GenericPrincipal类

一个主体(principal)使用 java.security.Principal 接口来表示,Catalina中的实现类是 org.apache.catalina.realm.GenericPrincipal。一个GenericPrincipal 必须跟一个域相关联,如下是GenericPrincipal的2个构造函数:

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

GenericPrincipal 必须拥有一个用户名和一个密码,此外还可选择性的传递一角色列表(List roles)给它。我们可以使用 hasRole()方法来检查一个 principal 是否有一个特定的角色,传递的参数为角色的字符串表示形式。如下是 Tomcat4 中的 hasRole() 方法:

public boolean hasRole(String role) {
    if (role == null)
        return (false);
    return (Arrays.binarySearch(roles, role) >= 0);
}

Tomcat5 支持 servlet2.4 所以必须支持用*来匹配任何角色。

public boolean hasRole(String role) {
    if ("*".equals(role)) // Special 2.4 role meaning everyone
        return true;
    if (role == null)
        return (false);
    return (Arrays.binarySearch(roles, role) >= 0);
}

10.3 LoginConfig类

一个登录配置包括一个域名,这里是通过org.apache.catalina.deploy.LoginConfig 不变类表示。LoginConfig 的实例封装了域名和验证要用的方法。可以通过LoginConfig实例的 getRealmName()方法来获得域名,可以使用 getAuthName()方法来验证用户。一个验证的名字必须是下面的之一:BASIC, DIGEST, FORM或者 CLIENT-CERT。如果用到的是基于表单(form)的验证,该 LoginConfig 对象还包括登录或者错误页面像对应的 URL。

Tomcat 在启动时,会先读取 web.xml。如果 web.xml 包括一个login-config元素,那么Tomcat 则会创建一 LoginConfig 对象并相应地设置它的属性。验证阀门调用 LoginConfig 的 getRealmName() 方法并将域名发送给浏览器显示登录表单。如果 getRealmName()名返回值为 null,则将发送服务器的名字和端口名给浏览器作为代替。图 10.1 是在Window XP 上 IE6 浏览器上显示的基本验证会话窗。
这里写图片描述
图10.1: The basic authentication dialog

10.4 验证器(Authenticator)

org.apache.catalina.Authenticator 接口用来表示一个验证器。该接口并没有方法,只是一个组件的标志器,这样就能使用 “instanceof”来检查一个组件是否为验证器。

Catalina 提供了 Authenticator 接口的基本实现:org.apache.catalina.authenticator.AuthenticatorBase 类。除了实现Authenticator 接口外,AuthenticatorBase 还继承了org.apache.catalina.valves.ValveBase 类。这就是说 AuthenticatorBase 也是一个阀门。可以在 org.apache.catalina.authenticator 包中找到该接口的几个类:BasicAuthenticator 用于基本验证, FormAuthenticator 用于基于表单的验证, DigestAuthentication 用于摘要(digest)验证, SSLAuthenticator 用于 SSL 验证。NonLoginAuthenticator 用于 Tomcat 没有指定验证元素的时候。NonLoginAuthenticator 类表示只是检查安全限制的验证器,但是不进行用户验证。

org.apache.catalina.authenticator 包中类的 UML 结构图如图 10.2 所示:
这里写图片描述
图10.2: Authenticator-related classes

一个验证器的主要工作是验证用户。因此AuthenticatorBase 类的 invoke 方法调用了抽象方法 authenticate()也不足为奇了,该方法的具体实现由子类完成。例如,在BasicAuthenticator中,authenticate()方法使用基本认证来认证用户。

10.5 安装验证器阀门

在部署文件中,只能出现一个 login-config 元素,login-config 元素包括了auth-method 元素用于定义验证方法。这也就是说一个上下文容器只能有一个LoginConfig 对象来使用一个 authentication 的实现类。

具体AuthenticatorBase 的子类哪一个在上下文中被用作验证阀门,这依赖于部署文件中auth-method 元素的值。表 10.1 为 auth-method 元素的值,可以用于确定验证器。

表10.1: The authenticator implementation class

Value of the auth-method elementAuthenticator class
BASICBasicAuthenticator
FORMFormAuthenticator
DIGESTDigestAuthenticator
CLIENT-CERTSSLAuthenticator

如果没有使用 auth-method 值,则认为 LoginConfig 对象的 auth-method 属性值为NONE,那么NonLoginAuthenticator类将被使用。

因为验证器类只在运行时知道,所以类是动态加载的。 StandardContext类使用org.apache.catalina.startup.ContextConfig类来配置StandardContext实例的许多设置。 此配置包括实例化验证器类并将该实例与上下文相关联。本章应用Demo附带着一个简单的上下文配置类:ex10.pyrmont.core.SimpleContextConfig。 正如你所见,这个类的实例负责动态加载BasicAuthenticator类,实例化它,并将其安装为StandardContext实例中的一个阀门。

注意:我们将在第15章讨论org.apache.catalina.startup.ContextConfig类。

10.6 应用Demo

本章附带的应用程序中使用了几个与安全约束相关的Catalina类。还使用了类似于第9章中的SimplePipeline,SimpleWrapper和SimpleWrapperValve类。此外,SimpleContextConfig类与第9章中的SimpleContextConfig类也相似,除了它具有authenticatorConfig()方法,该方法向StandardContext添加一个BasicAuthenticator实例 。 这2个应用Demo还使用了PrimitiveServlet和ModernServlet。 这些类在2个附带的应用Demo中使用。

第1个Demo中使用ex10.pyrmont.startup.Bootstrap1和ex10.pyrmont.realm.SimpleRealm 2个类;第二个Demo中使用了ex10.pyrmont.startup.Bootstrap2 ex10.pyrmont.realm.SimpleUserDatabaseRealm。每个类将在下面子章节中介绍。

10.6.1 ex10.pyrmont.core.SimpleContextConfig类

Listing10.1中的SimpleContextConfig类与第9章中的SimpleContextConfig类似。org.apache.catalina.core.StandardContext实例需要将其配置属性设置为true。 但是,本章中的SimpleContextConfig类添加了从lifeCycleEvent()方法调用的authenticatorConfig()方法。 authenticatorConfig()方法实例化BasicAuthenticator类,并将其作为阀门添加到StandardContext实例管道中。

Listing 10.1: The SimpleContextConfig class

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) {
    }
  }
}

开始,authenticatorConfig()方法检查在相关联的上下文中是否存在安全约束。如果没有,则该方法直接返回而不安装验证器。

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

如果存在安全约束,则继续检测上下文中是否存在LoginConfig对象,如果不存在,则创建一个:

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

然后,authenticatorConfig()方法检查StandardContext对象管道中的基本阀门或附加阀门是否是验证器。 由于上下文只能有一个验证器,所以如果其中一个阀门是认证器,则authenticatorConfig()方法将返回。

// 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)是否已与上下文相关联。 如果没有发现领域,则不需要安装验证器,因为用户不能被认证:

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

此时,authenticatorConfig()方法将动态加载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) {}

10.6.2 ex10.pyrmont.realm.SimpleRealm类

Listing 10.2中的SimpleRealm类演示了领域是如何工作。这个类在本章的第一个Demo中使用,包含两个硬编码的用户名和密码。

Listing 10.2: The SimpleRealm Class

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

  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()方法创建2个用户。 在内部,用户由内部类User表示。 第一个用户具有用户名ken和密码blackcomb。 这个用户有两个角色:manager和programmer。 第二个用户的用户名和密码分别为cindy和bamboo。 这个用户拥有programmer的角色。 然后,这2个用户被添加到变量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);

SimpleRealm类提供了四种重载验证方法中的1个实现:

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

此authenticate()方法由验证器调用。 如果用户名和密码作为参数传递的用户不是有效的用户,则返回null。否则,它返回表示用户的Principal对象。

10.6.3 ex10.pyrmont.realm.SimpleUserDatabaseRealm类

SimpleUserDatabaseRealm类表示更复杂的领域。 它没在代码中存储用户列表。相反,它读取conf目录中的tomcat-users.xml文件,并将内容加载到内存中。然后针对该列表进行认证。 在随附zip文件的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="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="admin" password="password" roles="admin,manager"/>
</tomcat-users>

类SimpleUserDatabaseRealm 代码如Listing 10.3:

Listing 10.3: The SimpleUserDatabaseRealm class

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?
    // FIXME - Update all realms to support encoded passwords
    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));
  }

  // ------------------------------------------------------ Lifecycle Methods


    /**
     * Prepare for active use of the public methods of this Component.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents it from being started
     */
  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()方法。 createDatabase()方法实例化org.apache.catalina.users.MemoryUserDatabase读取并解析XML文档。

10.6.4 ex10.pyrmont.startup.Bootstrap1类

Bootstrap1在本章第一个Demo中使用,代码如Listing 10.4:

Listing 10.4: The Bootstrap1 Class

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

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

在Bootstrap1的main()方法中创建了PrimitiveServlet和ModernServlet对应的SimpleWrapper。

然后创建设置StandardContext,添加SimpleContextConfig监听器等基本如第9章一样。如下代码是新的:

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

main()方法创建一个SecurityCollection对象并调用其addPattern()和addMethod()方法。addPattern()方法指定安全性的URL约束。addMethod()方法添加受此限制的方法约束。addMethod()方法获取GET,因此HTTP请求的GET方式将受此安全约束的制约。

接下来,main()方法实例化SecurityConstraint类并将其添加到集合中。它还设置了可以访问受限资源的角色。通过manager,那些拥有manager角色的用户将能够查看资源。注意在SimpleRealm类只有用户ken具有manager角色,他的密码是blackcomb。

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

接下来,创建了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验证认证。只有在他/她键入正确的用户名和密码(在此,ken和blackcomb),他/她将被允许访问。

10.6.5 ex10.pyrmont.startup.Boo tstrap2类

Bootstrap2类启动第2个应用Demo。除了它使用SimpleUserDatabase实例作为领域关联到StandardContext外,几乎与Bootstrap1类类似。 要访问PrimitiveServlet和ModernServlet,正确的用户名和密码分别是:admin和password。

Listing 10.5: The Bootstrap2 class

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

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

10.6.6 运行Demo

在 windows 下运行第一个Demo,可以在工作目录下面如下运行该程序:

java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./ex10.pyrmont.startup.Bootstrap1

在 Linux 下,使用冒号分开两个库:

java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ex10.pyrmont.startup.Bootstrap1

在 windows 下运行第二个Demo,可以在工作目录下面如下运行该程序:

java-classpath ./lib/servlet.jar;./lib/commons-collections.jar;./lib/commons-digester.jar;./lib/commons-logging.jar;./ex10.pyrmont.startup.Bootstrap2

在 Linux 下,使用冒号分开两个库:

java-classpath ./lib/servlet.jar:./lib/commons-collections.jar:./lib/commons-digester.jar:./lib/commons-logging.jar :./ex10.pyrmont.startup.Bootstrap2

在二个Demo中,调用PrimitiveServlet和ModernServlet,可以分别使用下面的 URL 来请求:

http://localhost:8080/Primitive
http://localhost:8080/Modern

10.7 小结

安全性是servlet编程和servlet规范中的一个重要主题,通过提供安全相关的对象来满足安全的需要,例如主体(principal),角色(roles),安全约束(securityConstraint),登录配置(login Config)等。在本章中,我们已经学习了解到servlet容器如何解决安全性问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值