7.2 Class Loaders

 <  Day Day Up  >  

A class loader is responsible for loading classes from a specific location. At runtime, multiple class loaders may be in effect at any given time. The set of classes loaded by a particular class loader is known as that class loader's name space .

To run a Java program, the JVM needs to locate and load into memory the classes comprising that program. In a traditional execution environment, this service is provided by the loader and linker utilities, which load code from the file system in a platform-specific way. In the JRE, things are complicated by the fact that code may be loaded from multiple locations, some of which may be remote, unsecure, or untrusted. Therefore, loading classes into the JVM at runtime has several security implications.

7.2.1 Security Responsibilities of the Class-Loading Mechanism

Class loaders are the gatekeepers of the JVM, controlling which bytecodes may be loaded and which should be rejected. As such, class loaders have a number of security responsibilities:

  • Name-space separation. Separating name spaces prevents intentional and unintentional name- clash problems.

  • Package-boundary protection. Class loaders can refuse to load untrusted classes into the core Java packages, which contain the trusted system classes and other restricted packages.

  • Access-right assignment. Class loaders have the ability to associate a set of authorizations with each loaded class. In Java parlance, an authorization , or permission, is the right to access a protected resource. In the Java 2 platform, authorizations are represented as objects of type java.security.Permission . The JVM administrator can use a security policy database to specify what Permission s loaded code is granted. This association is the basis for runtime authorization checking for access to resources.

  • Search-order enforcement. The class-loading mechanism enforces a search order that prevents trusted classes from being replaced by classes from less trusted sources.

The class-loading mechanism has another useful side effect. By controlling how the JVM loads code, all platform-specific file I/O involved in the loading of the classes is channeled through one part of the JVM, thus making porting the JVM to other platforms a much simpler task.

7.2.2 Levels of Trustworthiness of Loaded Classes

Java code can be loaded from various sources. Some of the more common sources, listed from most trusted to least trusted, are

  1. The core classes that ship with the JVM: for example, those in packages java.lang , java.io , java.net , java.util , and so on. These are also known as the system classes . By default, system classes are the only ones to be considered fully trusted. As such, they are not subjected to any security restrictions; nor is their integrity verified by the class file verifier.

  2. Any installed JVM extensions, such as cryptographic service providers (CSPs), XML parsers, and so on.

  3. Classes stored in the local file system, usually found using the CLASSPATH system environment variable.

  4. Classes retrieved from external sources, such as remote Web servers.

Given that code running on a JVM can come from trusted and untrusted sources, the class-loading mechanism must guarantee

  • Protection of trusted classes. Clearly, a trusted system class should not be overwritten with an identically named class downloaded from a remote, untrusted location, as this would undermine the security of the entire system. For instance, the default SecurityManager class ”the one in package java.lang ”is responsible for a large part of the JVM runtime security and is a trusted local class. Consider what would happen to the JVM's security if the default SecurityManager could be replaced by a class loaded from an untrusted, remote site. The class-loading mechanism must therefore ensure that where a name clash occurs, trusted local classes are loaded in preference to remote or untrusted classes.

  • Protection against name collisions. Especially where classes are loaded from remote locations, a deliberate or unintentional collision of names could occur, although the Sun Microsystems Java naming conventions [1] exist to prevent unintentional name collisions. If two versions of a class exist and are used by different programs loaded from different URLs, the JVM, through the auspices of the class-loading mechanism, must ensure that the two classes can coexist without any possibility of confusion occurring.

    [1] See http://java.sun.com.

  • Protection of trusted packages. The class-loading mechanism must protect the boundaries of the trusted class packages and sealed packages. [2] The core Java class libraries that ship with the JVM reside in a series of packages. Because the Java programming language grants special access privileges to classes that reside in the same package, a class that is part of the java.lang package, for instance, has access to other classes' methods and fields that are not accessible to classes outside of the java.lang package. If it were possible for a programmer to add extraneous classes to the java.lang package, those classes would gain privileged access to the core java.lang classes. This would be an exposure of the JVM and consequently must not be allowed. The class-loading mechanism, therefore, ensures that classes cannot be dynamically added to the various core-language and sealed packages. This result is accomplished by forcing class loaders to check with the SecurityManager every time a request comes to load a class in one of the system or sealed packages.

    [2] When a Java package is sealed , all the classes defined in that package originate from the same JAR file. When a JAR file is sealed , all the packages defined in that JAR file are sealed. Whether or not a JAR file or a package inside it are sealed can be specified in the manifest file of the JAR file itself. Sealing a package is important because it guarantees that classes external to the JAR file where the sealed package is defined cannot declare themselves as part of that package.

  • Name-space isolation. The class-loading mechanism is responsible for isolating unrelated mobile programs, by placing them in different name spaces. Each class loader is responsible for loading classes from a specific set of URLs. This allows different classes having the same fully qualified name but loaded from different locations to coexist without any possibility of confusion.

    As discussed in Section 7.2.3.2 on page 216, Java class loaders are organized in a tree structure, as shown in Figure 7.4. If two name spaces correspond to class loaders that belong to different branches of the class-loading tree, those name spaces are isolated from each other. Classes loaded into isolated name spaces cannot interfere with each other. For example, if classes A and B are loaded into two isolated name spaces, as shown in Figure 7.4, A cannot directly instantiate B , invoke static methods on B or instance methods on objects of type B, refer to static fields on B or instance fields on objects of type B , or subclass B . More important, A and B are totally unaware of each other's presence. For this reason, they could even have the same fully qualified name without any possibility of confusion.

    Figure 7.4. Class-Loading Tree Structure

    graphics/07fig04.gif

As discussed in Section 7.2.3.2 on page 216, a correct implementation of the class-loading mechanism must enforce isolation for unrelated name spaces.

Figure 7.4 shows that the JVM may have many class loaders operating at any time, each of which is responsible for locating and loading classes from different sources. However, in general, these sources can be divided into two categories, trusted and untrusted.

7.2.2.1 Loading Classes from Trusted Sources

The primordial class loader , which is a built-in part of the JVM, is also known as the internal , or null , or default class loader . The primordial class loader is responsible for loading the trusted classes of the Java runtime.

Classes loaded by the primordial class loader are regarded as special insofar as they are not subjected to verification prior to execution. They are assumed to be well- formed , safe Java classes. In addition, they are not subjected to any security policy restrictions. In early Java versions, these classes were the JVM core classes, along with any classes that could be found using the CLASSPATH system environment variable. Obviously, if would-be attackers could somehow introduce a malicious class into the CLASSPATH of a JVM, they could cause serious damage. In the Java 2 platform, this exposure is minimized by removing the core-class path information from the CLASSPATH environment variable and subjecting all but the core classes to verification [3] and the security policy.

[3] In the J2SE reference implementation, it is also possible to subject the core classes to verification by using the -verify option of the java command or the -J-verify option of the appletviewer command.

The core Java 2 classes are located by using a JVM internal property [4] whose value is called the boot class path . This internal property is formed internally from install information. [5]

[4] In the J2SE reference implementation, this internal property is called sun.boot.class.path .

[5] In the J2SE reference implementation, the boot class path can be specified at runtime by the java command option -Xbootclasspath , which becomes -J-Xbootclasspath for the appletviewer command. At compile time, the javac command-line option - bootclasspath is available to cross-compile programs against a specified set of boot classes.

Some J2SE and J2EE implementations allow a system administrator to extend the set of the system classes by placing custom classes in a particular directory. [6] Therefore, if a request arrives to load one of the classes from this directory, the primordial class loader will have responsibility for loading it. From a security point of view, two things should be noted. First, if a request arrives to load a class, this directory, as part of the boot class path, is inspected before the extension class path, application class path, and any remote location. Therefore, classes in this directory can potentially replace extension and application classes, as well as classes located in the network. Second, classes in this particular directory are considered fully trusted, just like the system classes. They will not be subjected to security policy restrictions, nor will the class file verifier check them for safety and integrity. It is therefore recommended to use operating system protections to make sure that only trusted users are allowed to create this directory and add files to it.

[6] For example, in the J2SE reference implementation, this directory is called classes . The Java system administrator needs to explicitly create the classes directory under the JRE installation directory, which, for example, in a Java 2 SDK V1.4.1 installation on a Windows platform is by default C:\Program Files\Java\j2re1.4.1 . Once created, however, the classes directory is automatically considered part of the boot class path. By default, the classes directory is the last location where the primordial class loader looks for classes. Note, however, that from Java 2 SDK V1.3 onward, the classes directory exists for use by the SDK only and should not be used for application classes. Application classes should be placed in a directory outside the SDK directory hierarchy. That way, installing a new SDK does not force you to reinstall application classes. For compatibility with older versions, applications that use the classes directory as a class library will run in the current version, but there is no guarantee that they will run in future versions.

7.2.2.2 Loading Classes from Untrusted Sources

Classes from untrusted sources include application classes, extension classes, and classes from remote network locations.

Application Classes

The Java 2 platform bounds the scope of implicitly trusted classes to only the Java core classes. User classes are not considered fully trusted and are not loaded by the primordial class loader. At JVM start-up, the application class path information is copied from the CLASSPATH environment variable into a JVM internal property. [7] This property is used to start an instance of java.net.URLClassLoader , an implementation of java.lang.ClassLoader . [8] This instance, called the application class loader , is given a list of URLs generated from CLASSPATH , which it will use to locate and load user classes. The application class loader is also responsible for associating the appropriate Permission s with the loaded class, based on the security configuration defined by the JVM administrator. [9]

[7] In the J2SE reference implementation, the name of this internal property is java.class.path .

[8] A note on terminology: From now on, we say ClassLoader when it is clear that the loader is an implementation of java.lang.ClassLoader . The term class loader is used only to refer to a generic loader, which may include the primordial class loader.

[9] In most implementations, the application class path can also be set on the command line using the option -classpath (or -cp ). This will override the CLASSPATH environment setting.

Extension Classes

From a trust viewpoint, classes of the extension framework fall logically in between the fully trusted core classes, for which no policy Permission entries are required and no integrity and safety verification is performed, and the completely untrusted application classes, for which explicit policy Permission entries are required and integrity and safety verification is performed. This framework allows for the installation of JAR files in a specific extensions directory referenced by a JVM internal property. [10] The value of this property is called the extension class path . [11] A URLClassLoader instance, called the extension class loader , is created at JVM start-up and is responsible for loading installed extensions.

[10] In the J2SE reference implementation, the name of this internal property is java.ext.dirs .

[11] In the J2SE reference implementation, the extension class path by default contains only the subdirectory lib/ext of the Java home directory. However, this value can be changed at JVM start-up by using the java command-line option -Djava.ext.dirs and at compile time by using the javac command-line option -extdirs .

Extension classes are subjected to security policy restrictions as well as safety and integrity verification, just like application classes. However, by default, extension classes are typically granted java.security.AllPermission , corresponding to the right to access all the system resources. Therefore, system administrators should allow only trusted users to add files to the extension class path. System administrators can also decide to restrict the access rights granted to the extensions.

Classes from Remote Network Locations

In a Web environment, classes can be loaded from remote network locations. An additional ClassLoader instance is created to load remote classes from a specific set of URLs. [12] Classes from different URLs may result in multiple ClassLoader s being created to maintain separate name spaces. Therefore, there may be multiple instances of the same ClassLoader class at any given time.

[12] For example, the Java Applet Viewer application or a Web browser automatically create a URLClassLoader instance, called applet class loader, to load applet classes from one or more URLs.

On most networks, including the Internet, there are many Web servers from which classes could be loaded. Nothing prevents two webmasters from having different classes with the same name on their Web sites. Within a name space, duplicate fully qualified class names are prohibited ; in other words, a given instance of a ClassLoader cannot load multiple classes with the same fully qualified name. If we did not have a specific ClassLoader instance for any URL from which classes are loaded, we would very quickly run into problems when loading classes with the same fully qualified name from multiple sites.

Moreover, it is essential for the security of the JVM to separate classes from different sites so that the classes cannot inadvertently or deliberately cross-reference each other. Imagine what could happen if a mobile program downloaded from a bank's Web site to manage financial transactions became accessible to another mobile program downloaded from a hacker's Web site. Classes from different sites are separated by preventing cross-visibility between name spaces corresponding to different URLs, as shown in Figure 7.5. Because classes from separate Web sites are loaded into separate name spaces for which there is no cross-visibility, a class loaded from a particular URL ”the bank's Web site ”cannot be accessed by a class loaded from a different URL ”the hacker's Web site.

Figure 7.5. Name Space Isolation

graphics/07fig05.gif

Classes loaded from remote network locations are considered fully untrusted. Therefore, they are subjected to the class file verifier's checks for integrity and safety purposes. In addition, by default those classes will run confined in the sandbox, unless the security policy of the Java system in which they are loaded grants them explicit Permission s.

The class-loading mechanism is also used in a J2EE environment to achieve isolation. Some J2EE implementations give system administrators the ability to isolate J2EE applications from each other by loading each J2EE application in a different name space. In other implementations, it is possible to isolate each module; this means that each Web or Enterprise JavaBeans module is loaded into its own name space. Additionally, name-space separation allows loading the same J2EE application multiple times, each time in a different name space.

7.2.3 The Class-Loading Process

It should now be clear that many types of class loaders can be within a Java environment at any time. In addition, multiple instances of a particular type of ClassLoader may be operating at once. In this section, we look at how all the class loaders cooperate to load classes in a secure manner. First, we consider the problem from a design viewpoint. Then, we show how that design is implemented in the Java 2 platform and how it should be implemented by any Java product needing to develop a customized class-loading mechanism.

7.2.3.1 Enforcing the Correct Search Order: The Design

In this section, we look at some of the design aspects of the Java 2 class-loading architecture. In other words, we describe what is supposed to happen from the viewpoint of the Java architects .

Let us assume that, at a certain time during the execution of a program, class A , which was loaded by class loader x , makes a reference to class B , as shown in point 1 in Figure 7.6. When class B is referenced, the JVM execution environment receives the request (point 2 in Figure 7.6) and invokes class loader x , associated with the requesting program A, to locate and load the referenced class (point 3 in Figure 7.6). Class loader x is associated with A because x was the class loader instance that loaded A 's class. Identifying the ClassLoader instance that loaded A is as simple as invoking the method getClassLoader() on A , as depicted in Figure 7.6. If A was loaded by the primordial class loader, invoking getClassLoader() returns null .

Figure 7.6. Class-Loading Process Scenario

graphics/07fig06.gif

Once x has received the request to load B, x becomes responsible for loading B (point 4 in Figure 7.6). First, x checks whether it had already loaded B . Loaded classes are cached, and there is no reason to load them again. If x had already loaded B, x interacts with the SecurityManager to see whether class A has the Permission to access B . If A does not have the Permission , a java.lang.SecurityException is thrown. If, however, A has the right Permission , x returns a reference to the existing Class object.

If B has not already been loaded, x invokes the SecurityManager to see whether A has the Permission to create the requested class, B . If it does not, a SecurityException is thrown. Otherwise, x first tries to find the requested class in the core Java API in the boot class path. This step prevents the JVM's core classes from being replaced by classes from another, less trusted location. If class B is found, it is loaded by the primordial class loader into the class area (see Figure 7.1 on page 204), and a reference to the Class object is returned to A .

If B is not found in the boot class path, x will try to find it in any JVM extensions located in the extension class path. If class B is found, it is loaded by the extension class loader into the class area, and a reference to the Class object is returned to A . Extensions are less trusted than the core Java classes but more trusted than the local application classes and the classes loaded from remote network locations. The mechanism we have described so far prevents extension classes from replacing the more trusted system classes and from being replaced by classes from less trusted locations.

If we have come to this point without finding B, x will look through the application class path before going to the network to locate the class. If it is found in the application class path, the class is loaded by the application class loader, and a reference to the Class object is returned to A . Otherwise, the ClassLoader responsible for finding classes in the network will look for B in the network locations from which it is responsible for loading classes. If this ClassLoader also fails to find the requested class, other user-defined ClassLoader s (see Section 7.2.4 on page 219) may be consulted to see whether they can find it in the other locations from which they are responsible for loading classes: for example, the ROM memory, a database, and so on. If none of the class loaders that received the request can find B, x will throw a java.lang.ClassNotFoundException .

If B is found, the class file verifier is responsible for making sure that the class file is well formed (see Section 7.3.1 on page 226). If the bytecode passes verification, the class is loaded into the class area, a Class object is created, and a set of Permission s is associated with the class for subsequent resource authorization checking. B is then linked by resolving any references to other classes within it. This may result in additional calls to B 's class loader to locate and load other classes. Next, static initialization of class B is performed; that is, static variables are defined and static initializers are run. Finally, the class is available to be executed, and a reference to it is returned to A .

The steps described in the preceding paragraph are performed regardless of the actual ClassLoader that loaded B . The only exceptional case is if B is one of the system classes in the boot class path, which ultimately implies that B 's class loader is the primordial loader. As system classes are considered fully trusted and safe to run, the primordial loader will not pass B to the class file verifier (see Figure 7.1 on page 204). In addition, given that system classes are implicitly granted full access to all the system resources, the primordial loader will not associate a set of Permission s with B . Except for these two security- related steps, the rest of the process is the same.

7.2.3.2 The Class-Loading Delegation Hierarchy: The Implementation

Every ClassLoader 's class, being just another Java class itself, is loaded by a class loader. The primordial class loader, which in general is not a Java class, is not loaded by any class loader and is generated at JVM start-up, and its primary function is to load the Java core classes. This forms a runtime parent/child hierarchical tree relationship between class loaders, with the primordial class loader at the root. This relationship is the basis for the delegation model , which is the recommended implementation model for all ClassLoader s. Every ClassLoader instance, on receiving the request to load a Java class, should immediately delegate the request to its parent class loader .

By default, when a program instantiates a ClassLoader object, the program's class loader becomes the ClassLoader object's parent. For example, in the J2SE reference implementation, the extension class loader is created at JVM start-up by one of the JVM's internal system programs, whose class loader is the primordial class loader. Therefore, the extension class loader's parent is the primordial class loader.

It is also possible to specify the parent ClassLoader explicitly via a parameter to the ClassLoader 's constructor. In this case, the specified ClassLoader is forced to be the parent. For example, at JVM start-up, in the J2SE reference implementation, the same system program that creates the extension class loader also creates the application class loader. Therefore, the primordial class loader should be the application class loader's parent. However, when the application class loader is constructed , the extension class loader is passed as a parameter to the constructor and becomes the application class loader's parent.

If a program creates a user-defined ClassLoader x, x 's parent is by default going to be the application class loader. The parent/child hierarchical tree relationship between class loaders in this scenario is shown in Figure 7.7.

Figure 7.7. Delegation Hierarchy Scenario

graphics/07fig07.gif

The runtime parent/child relationship between class loaders always has the primordial class loader at the root and forms the basis for the recommended implementation of the class-loading design. Figure 7.8 illustrates the security advantages coming from the delegation model.

Figure 7.8. Referencing Classes in the Delegation Hierarchy

graphics/07fig08.gif

Figure 7.8 shows how the class-loading delegation model guarantees the following.

  • A more trusted class cannot be replaced with a less trusted class having the same fully qualified name. In particular, system classes cannot be maliciously replaced with less trusted classes.

  • A class A and its instances can directly reference a class B and its instances if both A and B are loaded by the same class loader, x . For example, any two objects of the same application can reference each other because their classes are part of the same name space. According to the delegation model, the request for B will percolate up to the primordial class loader and then down back to x , which will load B , unless this had already been loaded and cached, in which case it would be simply returned to A .

  • A class C and its instances can directly reference a class D and its instances if C was loaded by a class loader y and D was loaded by any of y 's ancestor class loaders, w . For example, an application object can call java.lang.System.out.println() because the System class belongs to the name space of the primordial class loader, which is an ancestor of the application class loader.

    According to the delegation model, the request for D will percolate up to the primordial class loader, unless w had previously loaded D , in which case w will be able to satisfy the request for D on receiving it, without having to delegate it. If, however, the request for D reaches the primordial loader, as D is not in the boot class path, the primordial loader will propagate the request for D back down through all the descendant ClassLoader instances recursively, until descendant ClassLoader x is found that can find the requested class in its own class path, load it, and return it.

  • A class E and its instances cannot reference a class F and its instances if E was loaded by a class loader x and F was loaded by a class loader y , where y is one of x 's proper descendants. According to the delegation model, the request for F will percolate from x up to the primordial class loader and then back down back to x through all the descendant ClassLoader instances in the delegation chain recursively. None of these class loaders can find F because F is known only to y , and y is not a class loader in the delegation chain.

The class-loading delegation model additionally guarantees that programs whose classes are located in name spaces belonging to different branches of the class-loading delegation tree cannot reference each other, as shown in point 1 in Figure 7.9. This prevents cross-visibility and allows isolation between name spaces belonging to different branches of the delegation tree. Classes loaded in such name spaces are totally unaware of each other, as depicted in the scenario of Figure 7.5 on page 214. However, as shown in Figure 7.8, a class C and its instances can directly reference a class D and its instances if C was loaded by a class loader y and D was loaded by any of y 's ancestor class loaders, w . This scenario is also represented in point 2 in Figure 7.9. This implies that though isolated, two classes belonging to different branches of the delegation tree can still theoretically exchange information by setting and getting static state information on a program whose classes have been loaded by a common class loader ancestor. Therefore, developers of core and extension classes, whose class loaders are ancestors for all the other class loaders, should avoid writing classes whose state information can be mutated and subsequently retrieved by application code.

Figure 7.9. Name Space Isolation through the Class-Loading Delegation Model

graphics/07fig09.gif

7.2.4 Building a Customized ClassLoader

Application writers, including JVM implementers and J2EE container providers, may subclass ClassLoader to handle the loading of classes from different sources ”the Internet, an intranet, local storage, or perhaps even from read-only memory (ROM) in an embedded system ”or to implement additional functions that built-in ClassLoader s do not provide. These functions may include security and auditing. Custom ClassLoader s are not a part of the JVM but rather are part of an application running on top of the JVM.

The ability to create additional ClassLoader s is a very powerful feature of Java technology and places a heavy responsibility on the ClassLoader implementer. This feature becomes particularly apparent when you realize that user-written ClassLoader s have the choice of following the delegation model or not. They get first choice on whether to load a class. They can even take priority over the primordial class loader. This enables a user-written ClassLoader to replace any of the system classes, including the SecurityManager . A customized ClassLoader could even refuse to pass a loaded class to the class file verifier for integrity and safety verification. In addition, because the ClassLoader is responsible for associating Permission s with each loaded class, a customized ClassLoader could decide to assign full access to all the system resources to any loaded class. This shows the high degree of responsibility that ClassLoader designers and developers have and justifies the reason why, in order to install a new ClassLoader at runtime, programs need to be granted a specific Permission .

Starting with the Java 2 platform, the JRE includes a ClassLoader implementation, java.security.SecureClassLoader , which implements the basic security-related requirements of class loading: checking with the SecurityManager , calling the class file verifier, linking the class, and associating it with the Permission s that the Java system administrator granted to the class. Its constructor is protected; SecureClassLoader is meant to be the basis for the development of other class loaders.

URLClassLoader is a general-purpose loader included in the core Java 2 API. URLClassLoader , a subclass of SecureClassLoader , has the ability to find and load class files from a list of URLs. URLClassLoader should meet most of the requirements an application may have for loading class files. If not, developers can easily develop their own ClassLoader s by subclassing SecureClassLoader or URLClassLoader , instead of the ClassLoader abstract class. This way, they can benefit from the function and security built into SecureClassLoader .

The Java 2 API also includes the java.rmi.server.RMIClassLoader class to support dynamic class loading with Java RMI. The methods in this class are static, so they can be called directly to load individual unsigned class files from a single URL. Once a class file has been loaded, the RMIClassLoader provides the functionality to define a class from the loaded file. The RMIClassLoader 's name is misleading, as this ClassLoader is much more general purpose than its name implies and can be used to simply load class files. The RMIClassLoader can support several protocols, including HTTP and IIOP.

With the Java 2 existing class-loading architecture, there is much less reason to implement a customized ClassLoader . Instances of class URLClassLoader can load classes from a list of URLs. The instances can process class files, JAR files, and signed JAR files; can handle associating Permission s with loaded classes; and are programmed to interact with the SecurityManager during class loading to verify that a class has the right to trigger a reference to another class and that core packages are protected. Finally, URLClassLoader instances obey the class-loading delegation model, so they preserve the various levels of class trustworthiness by preventing less trusted classes from replacing more trusted classes with the same fully qualified name.

If, after all this, you still have reason to build your own ClassLoader , such as one that performs class-access auditing or works across a network protocol unsupported by the existing ClassLoader s, you can still benefit from subclassing one of the provided classes. For instance, if you are not using HTTP but everything else is the same, you can implement your own ClassLoader based on SecureClassLoader , and model it after URLClassLoader .

The example we show in this section is a Java 2 ClassLoader , Audit2ClassLoader , that extends URLClassLoader to provide class-access auditing, a feature that existing ClassLoader s do not regularly implement. It is interesting to note how much less work is required on the developer's part to write a Java 2 ClassLoader . By simply subclassing URLClassLoader , developers can focus on implementing a particular behavior that their new ClassLoader should provide, with the major security features automatically inherited from the superclass.

Audit2ClassLoader overrides the loadClass() method in URLClassLoader , simply recording class load requests in a file, whose name is hard-coded as auditclasses.log , and then asks its parent ClassLoader to load the class (Listing 7.1). By using all of URLClassLoader 's function, the code for Audit2ClassLoader is very short, but it offers an elegant example of OO design and implementation.

Listing 7.1. Audit2ClassLoader.java
 import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; /**  * This class extends URLClassLoader by providing an  * additional class-auditing function.  */ public class Audit2ClassLoader extends URLClassLoader {    private DataOutputStream auditlog;    /**     * Public constructor. Calls URLClassLoader's constructor     * and opens a file for recording class load messages.     *     * @param urls an array or URL objects from which this     *        ClassLoader is responsible for loading classes.     */    public Audit2ClassLoader(URL[] urls)    {       super(urls);       try       {          auditlog = new DataOutputStream(             new FileOutputStream("auditclasses.log"));          auditlog.writeBytes("Audit Started:\n");       }       catch (IOException e)       {          System.err.println("Audit file not opened " +             "properly\n" + e.toString());       }    }    /**     * The method that actually loads a class file. This     * method is invoked to load a new class. The steps that it     * must carry out are to write a message to a log file and     * to call the parent findClass() method to load, verify,     * and resolve the class and associate Permissions with it.     *     * @param name the fully qualified name of the class to load     * @throws ClassNotFoundException if the class was     *      not found     */    public Class loadClass(String name)       throws ClassNotFoundException    {       try       {          auditlog.writeBytes("loading class " + name + "\n");       }       catch (IOException ioe)       {          System.err.println("Could not write to audit file\n"             + ioe.toString());       }       try       {          return super.loadClass(name);       }       catch (Exception e)       {          throw new ClassNotFoundException(name);       }    } } 

Audit2ClassLoader requires a list of URLs to be passed to its constructor. This is really a requirement of URLClassLoader , thus limiting the scope of where Audit2ClassLoader will look for user class files. Classes in the boot, extension, and application class paths will be found and loaded through delegation by the appropriate class loader, as follows .

  1. Audit2ClassLoader will handle the files not found by delegation and will look only in the URL list passed on to its constructor.

  2. The application class loader created at JVM start-up will handle the classes not found by the extension class loader and primordial class loader and that it can find in the application class path.

  3. The extension class loader created at JVM start-up will handle the classes not found by the primordial class loader and that it can find in the extension class path.

  4. The primordial class loader will handle all the core classes, which are located in the boot class path.

 <  Day Day Up  >  


Enterprise Java Security. Building Secure J2EE Applications
Enterprise Javaв„ў Security: Building Secure J2EEв„ў Applications
ISBN: 0321118898
EAN: 2147483647
Year: 2004
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net