There are a few details about class loaders that we haven't yet covered. These details are not directly related to the security aspects of the class loader, which is why we've saved them until now. If you're interested in the complete details of the class loader, we'll fill in the last few topics here.
6.4.1 Delegation
As we've mentioned, class loading follows a delegation model. This model permits a class loader to be instantiated with this constructor:
protected ClassLoader(ClassLoader parent)
Create a class loader that is associated with the given class loader. This class loader delegates all operations to the parent first: if the parent is able to fulfill the operation, this class loader takes no action. For example, when the class loader is asked to load a class via the loadClass( ) method, it first calls the loadClass( ) method of the parent. If that succeeds, the class returned by the delegate will ultimately be returned by this class. If that fails, the class loader then uses its original logic to complete its task, something like this:
public Class loadClass(String name) { Class cl;
cl = delegate.loadClass(name);
if (cl != null) return cl;
// else continue with the loadClass( ) logic }
You may retrieve the delegate associated with a class loader with the following method.
public final ClassLoader getParent( )
Return the class loader to which operations are being delegated.
The class loader that exists at the root of the class loader hierarchy is retrieved via this method:
public static ClassLoader getSystemClassLoader( )
Return the system class loader (the class loader that was used to load the base application classes). If a security manager is in place, you must have the getClassLoader runtime permission to use this method.
6.4.2 Loading Resources
A class loader can load not only classes, but any arbitrary resource: an audio file, an image file, or anything else. Instead of calling the loadClass( ) method, a resource is obtained by invoking one of these methods:
public URL getResource(String name)
public InputStream getResourceAsStream(String name) public URL findResource(String name)
Find the named resource and return either a URL reference to it or an input stream from which it can be read. Implementations of class loaders should look for resources according to their internal rules, which are typically (but need not be) the same rules as are used to find classes.
The getResource( ) method calls the getSystemResource( ) method; if it does not find a system resource, it returns the object retrieved by a call to the findResource( ) method (which by default will be null). The getResourceAsStream( ) method simply calls the
getResource( ) method and, if a resource is found, opens the stream associated with the URL.
public static URL getSystemResource(String name)
public static InputStream getSystemResourceAsStream(String name)
Find the named resource and return either a URL reference to it or an input stream from which it can be read. By default, these methods look for the resource on the classpath and return that resource (if found).
public final Enumeration getResources(String name) public Enumeration findResources(String name)
Return an enumeration of resources with the given name. In the first method, an enumeration of the local resources of all delegated class loaders (including the present class loader) is returned; in the second method, only the local resources of the present class loader are returned.
6.4.3 Loading Libraries
Loading classes with native methods creates a call to this method of the ClassLoader class:
protected String findLibrary(String libname)
Return the directory from which native libraries should be loaded.
This method is used by the System.loadLibrary( ) method to determine the directory in which the native library in question should be found. If this method returns null (the default), the native library must be in one of the directories specified by either the java.library.path or java.sys.library.path property; typically, these properties are set in a platform−specific way (e.g., from the LD_LIBRARY_PATH on Solaris or the PATH on Microsoft Windows).
However, custom class loaders can override that policy and require that libraries be found in some application−defined location. This prevents a user from overriding the runtime environment to specify an
alternate location for that library, which offers a slight security advantage. Note that if the user can write to the hardwired directory where the library lives, this advantage no longer exists: the user can simply overwrite the existing library instead of changing an environment variable to point to another library; the end result is the same.
6.5 Comparison with Previous Releases
In Java 2, version 1.3, no class loader can define a class that is in the java package (i.e., whose class name begins with java). In 1.2 and earlier releases, that restriction is not present (unless you code it into your own class loader). That's one reason why there is no class loader that uses the checkPackageDefinition( ) method (although that method was not used in 1.2 either).
In Java 1.1, class loading was significantly different. To begin, there are no code source or protection domain objects in 1.1, and classes do not carry associated permissions with them. Security in 1.1 is solely up to the security manager, and the class loader simply loads classes.
That means that the loadClass( ) method in 1.1 is very different. In particular, there is only one defineClass( ) method that it can call (the one without a protection domain argument), and it does not call the findClass( ) method. In order to write a class loader in 1.1, you must override the
loadClass( ) method and perform all your work in that method. We show an example of this in Appendix D.
The secure class loader and URL class loader were introduced in Java 2, version 1.2. In 1.1, the primordial class loader is used to load all classes on the classpath in addition to the classes in the core API.
Delegation was also introduced in Java 2, version 1.2. In 1.1, a custom class loader can make a special method call to load classes from the classpath, but there is no general hierarchy of class loaders.
In 1.1, the default behavior of the methods that retrieve resources is to return null.
The RMI class loader does not exist in Java 1.0.
6.6 Summary
The class loading mechanism is integral to Java's security features. Typically this integration is considered in light of the relationship between the class loader, the access controller, and the security manager. However, the class loader is important in its own right. The class loader must enforce the namespace separation between classes that are loaded from different sites (especially when these different sites are untrusted); this helps to enforce the security mechanisms of the Java language.
For sites that need a more flexible security policy, a custom class loader may be desirable. Custom class loaders allow the security policy to be modified as classes are defined; this is similar to (and compatible with) providing a new implementation of the Policy class. However, custom class loaders can bypass the policy class altogether, which means that they can define immutable security policies (though, of course, installing the class loader in the first place still requires that applications have the appropriate policy−based
permissions). In certain circumstances, this is easier than modifying and installing a new Policy class.
So far, we've examined the basic level of Java's security paradigm −− essentially, those features that make up the Java sandbox. We're now going to shift gears somewhat and begin our examination of the cryptographic features in the Java security package itself. The Java security package is a set of classes that were added to Java 1.1 (and expanded in Java 2, version 1.2); these classes provide additional layers of security beyond the layers we've examined so far. Although these classes do play a role in the Java sandbox −− they are the basis on which Java classes may be signed, and expanding the sandbox based on signed classes is a key goal of Java security −− they may play other roles in secure applications.
A digital signature, for example, can authenticate a Java class so that the security policy can allow that class greater latitude in the operations it can perform, but a digital signature is a useful thing in its own right. An HR department may want to use a digital signature to verify requests to change payroll data, an online subscription service might require a digital signature to process a change order, and so on. Thus, while we'll examine the classes of the Java security package from the perspective of what we'll be able to do with a signed class, the techniques we'll show will have broader applicability.
In order to use the classes of the security package, you don't need a deep understanding of cryptographic theory. This chapter will explain the basic concepts of the operations involved, which should be sufficient to understand how to use the APIs involved. On the other hand, one feature of the security package is that different implementations of different algorithms may be provided by third−party vendors. We'll explain how to go about providing such implementations, but it is assumed that readers who are interested in writing such an implementation already understand the mechanics of cryptography. Hence, we won't give any
cryptographically valid examples in those sections. If you're interested in this type of information, a good reference is Jonathan Knudsen's Java Cryptography (O'Reilly & Associates).
If you already have an understanding of the basics of digital signatures, encryption, and the need for authentication, you can skip this chapter, which provides mainly background information.