How tomcat works——10 安全性

来源:互联网 发布:java while循环 编辑:程序博客网 时间:2024/06/01 09:41

概述

一些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 element Authenticator class BASIC BasicAuthenticator FORM FormAuthenticator DIGEST DigestAuthenticator CLIENT-CERT SSLAuthenticator

如果没有使用 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.UserDatabaseRealmimport 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 constraintSecurityCollection 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 realmRealm 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容器如何解决安全性问题。

0 0
原创粉丝点击