【Tomcat】《How Tomcat Works》英文版GPT翻译(第十章)

2024-01-19 17:15:52 浏览数 (2)

Part1Chapter 10: Security

Part2Overview

Some contents of a web application are restricted, and only authorized users are allowed to view them, after they supplied the correct user name and password. The servlet technology supports applying security constraint to those contents via the configuration of the deployment descriptor (web.xml file). Now, in this chapter, we will look at how a web container supports the security constraint feature.

Web应用程序的一些内容是受限制的,只有经过授权的用户在提供正确的用户名和密码后才能查看它们。

Servlet 技术支持通过部署描述符(web.xml文件)的配置对这些内容应用安全约束。

现在,在本章中,我们将看看Web容器如何支持安全约束功能。

A servlet container supports security constraint through a valve called authenticator. The authenticator valve is added to a context's pipeline when the servlet container is started. Read Chapter 6 again if you have forgotten about how a pipeline works.

Servlet 容器通过称为验证器的阀门来支持安全约束。

当启动 Servlet 容器时,验证器阀门将添加到上下文的管道中。

如果您忘记了管道的工作原理,请再次阅读第6章。

The authenticator valve is called before the wrapper valve. The authenticator valve authenticates the user. If the user enters the correct user name and password, the authenticator valve calls the next valve, which displays the requested servlet. If the authentication fails, the authenticator valve returns without invoking the next valve. As a result of a failed authentication, the user does not see the requested servlet.

验证器阀门在包装器阀门之前被调用。验证器阀门对用户进行身份验证。

如果用户输入了正确的用户名和密码,验证器阀门将调用下一个阀门,以显示请求的servlet。

如果身份验证失败,验证器阀门将在不调用下一个阀门的情况下返回。由于身份验证失败,用户将无法看到请求的 servlet

The authenticator valve calls the authenticate method of the context's realm to authenticate the user, passing the user name and password. A realm has access to the collection of valid user names and passwords.

验证器阀门调用上下文领域的 authenticate 方法对用户进行身份验证,传递用户名和密码。

领域可以访问有效用户名称和密码的集合。

This chapter starts with the classes that represent the objects related to the security feature in servlet programming (realms, principals, roles, etc). It then presents an application that demonstrates how you can apply basic authentication to your servlets.

本章从在 servlet 编程中表示与安全功能相关的对象的类开始(领域、主体、角色等)。

然后介绍一个演示如何将基本身份验证应用于您的servlet的应用程序。

Note It is assumed that you are familiar with the security constraint concept in servlet programming, including principals, roles, realms, login configuration, etc. If you do not understand them, read my own Java for the Web with Senlets, JSP, and EJB or any other decent servlet programming book. 注意:假定您熟悉servlet编程中的安全约束概念,包括主体、角色、领域、登录配置等。如果您不理解这些概念,请阅读我的《Java Web编程与Senlets、JSP和EJB》或其他良好的servlet编程书籍。

Part3Realm

A realm is a component used for authenticating a user. It can tell you whether or not a pair of user name and password is valid. A realm is normally attached to a context, and a container can only have one realm. You attach a realm to a container by passing the realm to the setRealm method of the container.

领域是用于验证用户的组件。

它可以告诉您一对用户名和密码是否有效。领域通常附加在一个上下文中,一个容器只能有一个领域。

您可以通过将领域传递给容器的 setRealm 方法来将领域附加到容器上。

How does a realm know how to authenticate a user? Well, it contains all user names and passwords of valid users or it has access to the store that holds them. Where this information is stored depends on the realm implementation. In Tomcat, by default valid users are stored in the tomcat-users.xml file. However, you can use other realm implementation that authenticates against other sources, such as a relational database.

领域如何知道如何验证用户?

嗯,它包含所有有效用户的用户名和密码,或者它可以访问保存它们的存储区。

这些信息存储在哪里取决于领域的实现。

在Tomcat中,默认情况下,有效用户存储在 tomcat-users.xml 文件中。

但是,您可以使用其他验证源的领域实现,例如关系数据库。

In Catalina, a realm is represented by the org.apache.catalina.Realm interface. The most important methods are its four authenticate method overloads:

Catalina 中,领域由org.apache.catalina.Realm接口表示。

最重要的方法是其四个authenticate方法的重载:

代码语言:javascript复制
  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[]);

The first overload is normally used. The Realm interface also has the hasRole method whose signature is as follows:

通常使用第一个重载。

Realm 接口还有 hasRole 方法,其签名如下:

代码语言:javascript复制
public boolean hasRole(Principal principal, String role);

Also, the getContainer and setContainer methods are used to associate a realm with a container.

另外,getContainersetContainer 方法用于将一个领域与一个容器关联起来。

A base implementation for the Realm interface is provided in the form of the abstract class org.apache.catalina.realm.RealmBase. The org.apache.catalina.realm package also provides a number of implementation classes that extend RealmBase:JDBCRealm, JNDIRealm, MemoryRealm, and UserDatabaseRealm. By default, MemoryRealm is used. When the MemoryRealm is first started, it reads the tomcat-users.xml document. In the application accompanying this chapter, however, you will build a simple realm that stores user information in the object itself.

Realm 接口的基本实现以抽象类org.apache.catalina.realm.RealmBase的形式提供。

org.apache.catalina.realm 包还提供了一些扩展RealmBase的实现类:JDBCRealmJNDIRealmMemoryRealmUserDatabaseRealm

默认情况下,使用MemoryRealm。当 MemoryRealm 首次启动时,它会读取tomcat-users.xml文档。

然而,在本章节的应用程序中,您将构建一个简单的领域,将用户信息存储在对象本身中。

Note In Catalina, the authenticator valve calls the authenticate method of the attached realm to authenticate a user. 注意 在 Catalina 中,验证器阀门调用所附 Realmauthenticate 方法来验证用户。

Part4GenericPrincipal

A principal is represented by the java.security.Principal interface. Its implementation in Catalina is the org.apache.catalina.realm.GenericPrincipal class. A GenericPrincipal must always be associated with a realm, as shown by the two constructors of GenericPrincipal:

一个principal(主体)由java.security.Principal接口表示。

Catalina 中,它的实现是org.apache.catalina.realm.GenericPrincipal类。

一个 GenericPrincipal 必须始终与一个 realm(领域)相关联,如 GenericPrincipal 的两个构造函数所示:

代码语言:javascript复制

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

A GenericPrincipal must also have a name and a password. Optionally, you can pass a list of roles to it. You can then check if this principal has a specified role by calling its hasRole method, passing the string representation of the role. Here is the hasRole method in Tomcat 4.

GenericPrincipal 还必须有名称和密码。

此外,还可以向其传递角色列表。

然后,你可以调用该 principalhasRole 方法,通过角色的字符串表示来检查该 principal 是否具有指定的角色。

下面是 Tomcat 4 中的 hasRole 方法。

代码语言:javascript复制
    public boolean hasRole(String role) {
        if (role == null)
            return (false);
        return (Arrays.binarySearch(roles, role) >= 0);
    }


Tomcat 5 supports Servlet 2.4 and therefore must recognize that the special character * represents any role.

Tomcat 5 支持 Servlet 2.4,因此必须识别特殊字符 * 代表任何角色。

代码语言:javascript复制
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);
    }

Part5LoginConfig

A login configuration contains a realm name and is represented by the org.apache.catalina.deploy.LoginConfig final class. The LoginConfig instance encapsulates the realm name and the authentication method used. You obtain the realm name by calling the LoginConfig instance's getRealmName method and the authentication method by invoking its getAuthName method. The value of the authentication name must be one of the following: BASIC, DIGEST, FORM, or CLIENT-CERT. If form-based authentication is used, the LoginConfig object also contains the string representations of the URLs to the login and error pages in its loginPage and errorPage properties, respectively.

一个登录配置包含一个领域名称,并由 org.apache.catalina.deploy.LoginConfig 类表示。

LoginConfig 实例封装了领域名称和所使用的身份验证方法。通过调用LoginConfig实例的getRealmName方法可以获取领域名称,通过调用其getAuthName方法可以获取身份验证方法。身份验证名称的值必须是以下之一:BASIC、DIGEST、FORM或CLIENT-CERT。如果使用基于表单的身份验证,则LoginConfig对象还包含登录页面和错误页面的URL的字符串表示,分别存储在其loginPage和errorPage属性中。

In a deployment, Tomcat reads the web.xml file at start-up. If the web.xml file contains a login-config element, Tomcat creates a LoginConfig object and sets its properties accordingly. The authenticator valve calls the getRealmName method of the LoginConfig and sends the realm name to the browser to be displayed in the Login dialog. If the getRealmName method returns null, the server name and port is sent to the browser instead. Figure 10.1 shows a basic authentication login dialog in Internet Explorer 6 on Windows XP.

在部署过程中,Tomcat 在启动时读取web.xml文件。

如果web.xml文件包含一个login-config元素,Tomcat 将创建一个 LoginConfig 对象并相应地设置其属性。

认证器阀门调用 LoginConfiggetRealmName 方法,并将领域名称发送到浏览器以在登录对话框中显示。

如果 getRealmName 方法返回 null,则服务器名称和端口将发送到浏览器。

图10.1显示了 Windows XPInternet Explorer 6中的基本身份验证登录对话框。

image.png

Figure 10.1: The basic authentication dialog

图 10.1: 基本身份验证对话框

Part6Authenticator

The org.apache.catalina.Authenticator interface represents an authenticator. It does not have a method and acts as a marker so that other components can detect whether or not a component is an authenticator by using an instanceof test.

org.apache.catalina.Authenticator 接口表示一个认证器。

它没有方法,只是作为一个标记,以便其他组件可以通过使用instanceof测试来检测一个组件是否是认证器。

Catalina provides a base implementation of the Authenticator interface: the org.apache.catalina.authenticator.AuthenticatorBase class. In addition to implementing the Authenticator interface, AuthenticatorBase extends the org.apache.catalina.valves.ValveBase class. That means, AuthenticatorBase is also a valve. A number of implementation classes can be found in the org.apache.catalina.authenticator package, including the BasicAuthenticator class that can be used for basic authentication, the FormAuthenticator class for form-based authentication, DigestAuthentication for digest authentication, and SSLAuthenticator for SSL authentication. In addition, the NonLoginAuthenticator class is used if the user of Tomcat does not specify a value for the auth-method element. The NonLoginAuthenticator class represents an authenticator that only checks security constraints but does not involve user authentication.

Catalina 提供了 Authenticator 接口的基本实现:org.apache.catalina.authenticator.AuthenticatorBase类。除了实现Authenticator接口外,AuthenticatorBase 还扩展了org.apache.catalina.valves.ValveBase类。

这意味着 AuthenticatorBase 也是一个阀门。

org.apache.catalina.authenticator包中可以找到许多实现类,包括用于基本认证的 BasicAuthenticator类,用于基于表单的认证的 FormAuthenticator 类,用于摘要认证的DigestAuthentication类,以及用于SSL认证的 SSLAuthenticator 类。

此外,如果 Tomcat 的用户没有为 auth-method 元素指定值,则使用NonLoginAuthenticator类。

NonLoginAuthenticator 类表示一个只检查安全约束但不涉及用户认证的认证器。

The UML class diagram of the members of the org.apache.catalina.authenticator package is given in Figure 10.2.

图 10.2 给出了 org.apache.catalina.authenticator 软件包成员的 UML 类图。

image.png

Figure 10.2: Authenticator-related classes

图 10.2: 验证器相关类

The main job of an authenticator is to authenticate users. Therefore, it's not surprising that the invoke method of AuthenticatorBase calls the abstract method authenticate whose implementation depends on the child class. In BasicAuthenticator, for example, the authenticate method uses basic authentication to authenticate the user.

身份验证器的主要工作是对用户进行身份验证。

因此,AuthenticatorBaseinvoke 方法会调用抽象方法 authenticate,而 authenticate 的实现取决于子类,这并不奇怪。

例如,在 BasicAuthenticator 中,authenticate 方法使用基本身份验证对用户进行身份验证。

Part7Installing the Authenticator Valve( 安装验证器阀门)

Value of the auth-method element Authenticator class

auth-method 元素的值验证器类别

代码语言:javascript复制
BASIC BasicAuthenticator 
FORM FormAuthenticator 
DIGEST DigestAuthenticator 
CLIENT-CERT SSLAuthenticator

If the auth-method element is not present, the value of the auth-method property of the LoginConfig object is assumed to be NONE, and the NonLoginAuthenticator class will be used.

如果auth-method元素不存在,则假定 LoginConfig 对象的auth-method属性值为NONE,并使用NonLoginAuthenticator类。

Because the authenticator class is only known at runtime, the class is dynamically loaded. The StandardContext class uses the org.apache.catalina.startup.ContextConfig class to configure many settings of the StandardContext instance. This configuration includes the instantiation of an authenticator class and associating the instance with the context. The application that accompanies this chapter employs a simple context config of type ex10.pyrmont.core.SimpleContextConfig. As you can see later, the instance of this class is responsible for dynamically loading the BasicAuthenticator class, instantiating it, and installing it as a valve in the StandardContext instance.

由于认证器类只在运行时才知道,所以该类是动态加载的。StandardContext 类使用org.apache.catalina.startup.ContextConfig类来配置StandardContext实例的许多设置。

这个配置包括实例化认证器类并将实例与上下文关联起来。

本章附带的应用程序使用ex10.pyrmont.core.SimpleContextConfig类型的简单上下文配置。

稍后可以看到,这个类的实例负责动态加载 BasicAuthenticator 类、实例化它,并将其安装为 StandardContext 实例的一个阀门。

Note The org.apache.catalina.startup.ContextConfig class will be discussed in Chapter 15. 注意:org.apache.catalina.startup.ContextConfig类将在第15章中讨论。

Part8The Applications

The applications accompanying this chapter use several Catalina classes that are related to security constraint. Both also employ the SimplePipeline, SimpleWrapper, and SimpleWrapperValve classes that are similar to the application in Chapter 9. In addition, the SimpleContextConfig class is similar to the SimpleContextConfig class in Chapter 9, except that it has the authenticatorConfig method that adds a BasicAuthenticator instance to the StandardContext. Both applications also use two servlets: PrimitiveServlet and ModernServlet. These classes are used in both accompanying applications.

本章附带的应用程序使用几个与安全约束相关的 Catalina 类。

两个应用程序都使用了与第9章应用程序类似的 SimplePipelineSimpleWrapperSimpleWrapperValve 类。

此外,SimpleContextConfig 类与第9章中的 SimpleContextConfig 类类似,只是它有一个 authenticatorConfig 方法,该方法将 BasicAuthenticator 实例添加到 StandardContext 中。

两个应用程序还使用了两个 servletPrimitiveServletModernServlet

这些类在两个附带的应用程序中都使用。

There are two classes in the first application, ex10.pyrmont.startup.Bootstrap1and ex10.pyrmont.realm.SimpleRealm. The second application uses ex10.pyrmont.startup.Bootstrap2 and ex10.pyrmont.realm.SimpleUserDatabaseRealm. Each of these classes will be explained in the sections below.

第一个应用程序中有两个类:ex10.pyrmont.startup.Bootstrap1ex10.pyrmont.realm.SimpleRealm

第二个应用程序使用ex10.pyrmont.startup.Bootstrap2ex10.pyrmont.realm.SimpleUserDatabaseRealm

下面的小节将详细解释这些类。

1The ex10.pyrmont.core.SimpleContextConfig Class

The SimpleContextConfig class in Listing 10.1 is similar to SimpleContextConfig in Chapter 9. It is required by the org.apache.catalina.core.StandardContext instance to set its configured property to true. However, the SimpleContextConfig class in the application in this chapter adds the authenticatorConfig method that is called from the lifeCycleEvent method. The authenticatorConfig method instantiates the BasicAuthenticator class and adds it as a valve to the StandardContext instance's pipeline.

清单10.1中的 SimpleContextConfig 类与第9章中的 SimpleContextConfig 类类似。

org.apache.catalina.core.StandardContext实例需要它将其configured属性设置为true

然而,在本章的应用程序中,SimpleContextConfig 类添加了一个 authenticatorConfig 方法,该方法从 lifeCycleEvent 方法中调用。

authenticatorConfig 方法实例化 BasicAuthenticator 类,并将其作为一个阀门添加到 StandardContext 实例的管道中。

Listing 10.1: The SimpleContextConfig class

清单10.1:SimpleContextConfig

代码语言:javascript复制
  
    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) {  
            }  
        }  
    }

The authenticatorConfig method starts by checking if there is a security constraint in the associated context. If there is none, the method returns without installing an authenticator.

authenticatorConfig 方法首先会检查相关上下文中是否存在安全限制。

如果没有,该方法将返回,但不会安装身份验证器。

代码语言:javascript复制
// Does this Context require an Authenticator?
 SecurityConstraint constraints[] = context.findConstraints();
 if ((constraints == null) || (constraints.length == 0))
 return;

If one or more security constraint is found, the authenticatorConfig method checks if the context has a LoginConfig object. If it does not have a LoginConfig, an instance is created.

如果发现一个或多个安全限制,authenticatorConfig 方法会检查上下文是否有 LoginConfig 对象。

如果没有,就会创建一个实例。

代码语言:javascript复制
LoginConfig loginConfig = context.getLoginConfig();
 if (loginConfig == null) {
 loginConfig = new LoginConfig("NONE", null, null, null);
 context.setLoginConfig(loginConfig);
 }

The authenticatorConfig method then checks if the basic valve or another additional valve in the StandardContext object's pipeline is an authenticator. Since a context can only have one authenticator, the authenticatorConfig method will return if one of the valves is an authenticator.

然后,authenticatorConfig 方法会检查 StandardContext 对象管道中的基本阀门或另一个附加阀门是否是身份验证器。

由于一个上下文只能有一个身份验证器,因此如果其中一个阀门是身份验证器,authenticatorConfig 方法就会返回。

代码语言:javascript复制
// 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;
 }

It then checks if a realm has been associated with the context. If no realm is found, there is no need to install an authenticator because the user cannot be authenticated against.

然后,它会检查上下文是否关联了一个域。

如果没有找到域,则无需安装身份验证器,因为用户无法通过身份验证。

代码语言:javascript复制
// Has a Realm been configured for us to authenticate against?
 if (context.getRealm() == null) {
 return;
 }

At this point, the authenticatorConfig method will load the BasicAuthenticator class dynamically, creates an instance of the class, and adds it as a valve to the StandardContext instance.

此时,authenticatorConfig 方法将动态加载 BasicAuthenticator 类,创建该类的一个实例,并将其作为阀门添加到 StandardContext 实例中。

代码语言:javascript复制
// 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) { }
 }

2The ex10.pyrmont.realm.SimpleRealm Class

The SimpleRealm class in Listing 10.2 demonstrates how a realm works. This class is used in the first application in this chapter and contains two hard-coded user names and password.

清单 10.2 中的 SimpleRealm 类演示了 Realm 的工作原理。该类用于本章的第一个应用程序,包含两个硬编码的用户名和密码。

Listing 10.2: The SimpleRealm Class

清单 10.2:SimpleRealm

代码语言:javascript复制

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

The SimpleRealm class implements the Realm interface. From the constructor, it calls the createUserDatabase method that creates two users. Internally, a user is represented by the inner class User. The first user has the user name ken and password blackcomb. This user has two roles, manager and programmer. The second user's user name and password are cindy and bamboo, respectively. This user has the programmer role. Then, the two users are added to the users ArrayList. The code that creates the two users are as follows:

SimpleRealm 类实现了 Realm 接口。在构造函数中,它调用 createUserDatabase 方法来创建两个用户。

在内部,用户由内部类 User 表示。第一个用户的用户名是 ken,密码是 blackcomb

该用户有两个角色:经理和程序员。第二个用户的用户名和密码分别是 cindybamboo

该用户的角色是程序员。然后,这两个用户被添加到用户 ArrayList 中。创建这两个用户的代码如下:

代码语言:javascript复制
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);

The SimpleRealm class provides the implementation of one of the four authenticate method overloads.

SimpleRealm 类提供了四种身份验证方法重载之一的实现。

代码语言:javascript复制
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());
 }

This authenticate method is called by an authenticator. It returns null if the user whose user name and password are passed as arguments is not a valid user. Otherwise, it returns a Principal object representing the user.

该身份验证方法由身份验证器调用。

如果作为参数传递用户名和密码的用户不是有效用户,该方法将返回空值。

否则,它将返回一个代表该用户的 Principal 对象。

Part9The ex10.pyrmont.realm.SimpleUserDatabaseRealm Class

The SimpleUserDatabaseRealm class represents a more complex realm. It does not store the list of users in its body. Instead, it reads the tomcat-users.xml file in the conf directory and loads the content to the memory. Authentication is then conducted against this list. In the conf directory of the accompanying zip file, you can find a copy of the tomcat-users.xml file as follows:

SimpleUserDatabaseRealm 类代表了一个更复杂的领域。

它不会在主体中存储用户列表。

相反,它会读取 conf 目录中的 tomcat-users.xml 文件,并将内容加载到内存中。

然后根据该列表进行身份验证。在所附 zip 文件的 conf 目录中,你可以找到tomcat-users.xml文件的副本,如下所示:

代码语言:javascript复制
<?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>

The SimpleUserDatabaseRealm class is given in Listing 10.3.

清单 10.3 给出了 SimpleUserDatabaseRealm 类。

Listing 10.3: The SimpleUserDatabaseRealm class

清单 10.3:SimpleUserDatabaseRealm

代码语言:javascript复制

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

After instantiating the SimpleUserDatabaseRealm class, you must call its createDatabase method passing the path to the XML document containing the list of users. The createDatabase method instantiates the org.apache.catalina.users.MemoryUserDatabase class that performs the reading and parsing of the XML document.

实例化 SimpleUserDatabaseRealm 类后,必须调用其 createDatabase 方法,并将路径传给包含用户列表的 XML 文档。

createDatabase 方法会实例化org.apache.catalina.users.MemoryUserDatabase类,该类会执行 XML 文档的读取和解析。

3The ex10.pyrmont.startup.Bootstrap1 Class

The Bootstrap1 class starts the first application in this chapter. It is given in Listing 10.4.

Bootstrap1 类启动了本章的第一个应用程序。它在清单 10.4 中给出。

Listing 10.4: The Bootstrap1 Class

清单 10.4:Bootstrap1

代码语言:javascript复制
  
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();  
            }  
        }  
    }

The main method of the Bootstrap1 class creates two SimpleWrapper objects and calls their setName and setServletClass methods. To the first SimpleWrapper object, you pass Primitive to its setName method and PrimitiveServlet to its setServletClass method. The second SimpleWrapper object gets Modern and ModernServlet.

Bootstrap1类的主方法创建了两个 SimpleWrapper 对象,并调用它们的 setNamesetServletClass 方法。

对于第一个 SimpleWrapper 对象,您将在 setName 方法中传递 Primitive,在 setServletClass 方法中传递PrimitiveServlet。

第二个SimpleWrapper对象则分别传递ModernModernServlet

The main method then creates a StandardContext object, sets its path and document base, and adds a listener of type SimpleContextConfig. Remember that the latter will install a BasicAuthenticator in the StandardContext object. Next, it adds a loader to the StandardContext and two servlet mappings. You have seen the same code in the application in Chapter 9. The next lines of code are new.

接下来,主方法创建了一个 StandardContext 对象,并设置了其路径和文档基础目录,并添加了一个类型为 SimpleContextConfig 的监听器。

请记住,后者将在 StandardContext 对象中安装一个 BasicAuthenticator

接下来,它向 StandardContext 添加了一个加载器和两个 servlet 映射。

您在第9章的应用程序中已经见过相同的代码。接下来的代码是新的。

代码语言:javascript复制
// add constraint
 SecurityCollection securityCollection = new SecurityCollection();
 securityCollection.addPattern("/");
 securityCollection.addMethod("GET");

The main method creates a SecurityCollection object and calls its addPattern and addMethod methods. The addPattern method specifies the URL to which security constraint will apply. The addMethod method adds a method that is subject to this constraint. The addMethod method gets GET, so HTTP requests with a GET method will be subject to the security constraint.

主方法创建了一个 SecurityCollection 对象,并调用了它的 addPatternaddMethod 方法。

addPattern 方法指定了安全约束将应用的URL。

addMethod 方法添加了一个受此约束限制的方法。

addMethod 方法获取GET方法,因此使用GET方法的HTTP请求将受到安全约束的限制。

Next, the main method instantiates the SecurityConstraint class and adds it to the collection. It also sets the role that can have access to the restricted resources. By passing manager, those users in the manager role will be able to view the resources. Note that the SimpleRealm class has only one user with the manager role, ken. His password is blackcomb.

接下来,主方法实例化了 SecurityConstraint 类 ,并将其添加到集合中。

它还设置了可以访问受限资源的角色。通过传递 manager,那些在 manager 角色中的用户将能够查看资源。

请注意,SimpleRealm 类只有一个具有 manager 角色的用户,名为 ken。他的密码是 blackcomb

代码语言:javascript复制
SecurityConstraint constraint = new SecurityConstraint();
 constraint.addCollection(securityCollection);
 constraint.addAuthRole("manager");

Next, the main method creates a LoginConfig object and a SimpleRealm object.

接下来,主方法会创建一个 LoginConfig 对象和一个 SimpleRealm 对象。

代码语言:javascript复制
LoginConfig loginConfig = new LoginConfig();
 loginConfig.setRealmName("Simple Realm");
 // add realm
 Realm realm = new SimpleRealm();

Then, it associates the realm, constraint, and login config objects with the StandardContext instance:

然后,它会将领域、约束和登录配置对象与 StandardContext 实例关联起来:

代码语言:javascript复制
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);

Next, it starts the context. This part has been explained in the previous chapters. In effect, access to PrimitiveServlet and ModernServlet are now restricted.

接下来,它将启动上下文。

这部分内容已在前面几章中解释过。实际上,访问 PrimitiveServletModernServlet 现在受到了限制。

If a user requests any of the servlets, he/she must be authenticated using basic authentication. Only after he/she types in the correct user name and password (in this case, ken and blackcomb), will he/she be allowed access.

如果用户请求使用任何一个小程序,都必须使用基本身份验证进行验证。

只有输入正确的用户名和密码(本例中为 kenblackcomb)后,才允许访问。

4The ex10.pyrmont.startup.Bootstrap2 Class

The Bootstrap2 class starts up the second application. This class is similar to the Bootstrap1 class, except that it uses an instance of SimpleUserDatabase as a realm associated to the StandardContext. To access PrimitiveServlet and ModernServlet, the correct user name and password would be admin and password, respectively.

Bootstrap2 类启动第二个应用程序。这个类与B ootstrap1 类相似,只是它使用一个 SimpleUserDatabase 实例作为与 StandardContext 相关联的域。

要访问 PrimitiveServletModernServlet,正确的用户名和密码分别为 adminpassword

The Bootstrap2 class is given in Listing 10.5.

Bootstrap2 类如下所示:

Listing 10.5: The Bootstrap2 class

代码清单10.5:Bootstrap2

代码语言:javascript复制

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

Part10Running the Applications

To run the first application in Windows, from the working directory, type the following:

要在 Windows 中运行第一个应用程序,请在工作目录中键入以下内容:

代码语言:javascript复制
java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;./
ex10.pyrmont.startup.Bootstrap1

In Linux, you use a colon to separate two libraries.

在 Linux 中,使用冒号分隔两个库。

代码语言:javascript复制
java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:./ ex10.pyrmont.startup.Bootstrap1

To run the second application in Windows, from the working directory, type the following:

要在 Windows 中运行第二个应用程序,请在工作目录中键入以下内容:

代码语言:javascript复制
java -classpath ./lib/servlet.jar;./lib/commons-collections.jar;
./lib/commons-digester.jar;./lib/commons-logging.jar;./
ex10.pyrmont.startup.Bootstrap2

In Linux, you use a colon to separate two libraries.

Linux 中,使用冒号分隔两个库。

代码语言:javascript复制
java -classpath ./lib/servlet.jar:./lib/commons-collections.jar:
./lib/commons-digester.jar:./lib/commons-logging.jar :./
ex10.pyrmont.startup.Bootstrap2

To invoke the Primitive servlet in both the first and second applications, use the following URL in your browser.

要在第一个和第二个应用程序中调用 Primitive servlet,请在浏览器中使用以下 URL

http://localhost:8080/Primitive

To invoke the Modern servlet in both the first and second applications, use the following URL in your browser.

要在第一个和第二个应用程序中调用 Modern servlet,请在浏览器中使用以下 URL。

http://localhost:8080/Modern

Part11Summary

Security is an important topic in servlet programming and the servlet specifications cater for the need for security by providing security–related objects such as principal, roles, security constraints, login configuration, etc. In this chapter you have learned how a servlet container addresses this issue.

安全是 servlet 编程中的一个重要主题,Servlet 规范通过提供与安全相关的对象(如本金、角色、安全限制、登录配置等)来满足安全需求。

在本章中,你将了解到 servlet 容器是如何解决这一问题的。

0 人点赞