Now we have all the pieces in place to discuss the mechanics of the access controller. The access controller is represented by a single class called, conveniently, AccessController. There are no instances of the AccessController class (java.security.AccessController)--its constructor is private, so that it cannot be instantiated. Instead, this class has a number of static methods that can be called in order to determine if a particular operation should succeed. The key method of this class takes a particular permission and determines, based on the installed Policy object, whether or not the permission should be granted:
Check the given permission against the policy in place for the program. If the permission is granted, this method returns normally; otherwise, it throws an AccessControlException.
We can use this method to determine whether or not a specified operation should be permitted:
public class AccessTest extends Applet { public void init() { SocketPermission sp = new SocketPermission( getParameter("host") + ":6000", "connect"); try { AccessController.checkPermission(sp); System.out.println("Ok to open socket"); } catch (AccessControlException ace) { System.out.println(ace); } } }
Whether the access controller allows or rejects a given permission depends upon the set of protection domains that are on the stack when the access controller is called. Figure 5-2 shows the stack that might be in place when the init() method of the AccessTest applet is called. In the appletviewer, an applet is run in a separate thread--so the bottom method on the stack is the run() method of the Thread class.[2] That run() method has called the run() method of the AppletPanel class. This second run() method has done several things prior to calling the init() method: it first created an HTTP-based class loader (from an internal class that is a subclass of the URLClassLoader class) and has used that class loader to load the AccessTest class. It then instantiated an instance of the AccessTest class and called the init() method on that object. This left us with the stack shown in the figure--the run() method of the Thread class has called the run() method of the AppletPanel class, which has called the init() method of the AccessTest class, which has called the checkPermission() method of the AccessController class.
[2]In fact, the run() method is always the bottom method on a stack, since stacks apply on a per-thread basis.
The reason we need to know the stack trace of the current thread is to examine the protection domains that are on the stack. In this example, only the AccessTest class has been loaded by a class loader: the AppletPanel class and the Thread class were loaded from the core API with the primordial class loader. Hence, only the AccessTest class has a nonsystem protection domain (associated with the URL from which we loaded it, http://piccolo/ in this case).
The permissions for any particular operation can be considered to be the intersection of all permissions of each protection domain on the stack when the checkPermission() method is called. When the checkPermission() method is called, it checks the permissions associated with the protection domain for each method on the stack. It does this starting at the top of the stack, and proceeding through each class on the stack.
If this entry appeared in the policy file:
grant codeBase http://piccolo/ { permission java.net.SocketPermission "*:1024-", "connect"; }
the protection domain that applies to the AccessTest class will have permission to open the socket. Remember that the system domain implicitly has permission to perform any operation; as there are no other nonsystem protection domains associated with any class on the stack, the checkPermission() method will permit this operation--which is to say that it will silently return.
For most implementations of Java browsers, and many Java applications, there will only be a single nonsystem protection domain on the stack: all the classes for the applet will have come from a single CODEBASE (and hence a single protection domain). But the checkPermission() method is more general than that, and if you use a class loader that performs delegation, there will be multiple protection domains on the stack. This is a common occurrence if you're using a Java extension.
Let's say that you've written a payroll application that uses a class loader that loads classes from two sources: the server in the XYZ HR department and the server in the XYZ network services department.[3] This might lead to a call to the checkPermission() method with the stack shown in Figure 5-3. Note that this stack trace is a little more complicated than the one we've just shown--in this case, we're relying on the fact that the constructor of the Socket class will (indirectly) call the access controller. That is what actually happens, and we'll explore that process in our next chapter. For now, we'll just accept the fact that this is the correct stack trace.
[3]We showed this example and class loaders to implement it in Chapter 3, "Java Class Loaders".
In this example, the access controller first checks the protection domain for the Network class to see if a class loaded from http://network.xyz.com/ is allowed to connect to the socket. If that succeeds, it then checks the protection domain of the PayrollApp class to see if a class loaded from http://hr.xyz.com/ is allowed to connect to a the socket. Only if both code sources are granted permission in the policy file (either individually or via an entry that does not specify a code base at all) does the checkPermission() method succeed.
Whether or not this is the appropriate behavior depends upon your intent. Let's say that the policy file for the payroll application specifies that classes with a code base of http://network.xyz.com/ are allowed to create sockets, but that no other protection domains (other than the system protection domain, of course) are granted that permission. That leads to the situation where a class from the network services department might not be able to open a socket (even though it has that permission in the file): if there is any class in the HR protection domain on the stack, the operation will fail. All classes on the stack must have permission for an operation to succeed.
Often, however, you want a class to be temporarily given the ability to perform an action on behalf of a class that might not normally have that ability. In this case, we might want to establish a policy where the classes from the HR department cannot create a socket directly, but where they can call classes from the network services department that can create a socket.[4] In this case, you want to tell the access controller to grant (temporarily) the permissions of the network services department to any methods that it might call within the current thread.
[4]Consider this in terms of writing a file: an applet might not be able to write a file, but it can call a method of the JDK to play audio data--which means that the JDK class must write to the audio device file.
That facility is possible with these two methods of the access controller class:
Execute the run() method on any given object, temporarily granting its permission to any protection domains below it on the stack. In the second case, if the embedded run() method throws an exception, the doPrivileged() method will throw a PrivilegedActionException.
The PrivilegedAction and PrivilegedExceptionAction interfaces contain a single method:
Run the target code, which will have the permissions of the calling class.
The difference between the two interfaces is that the run() method in the PrivilegedExceptionAction interface may throw an arbitrary exception. Note the unfortunate overloading between this method and the run() method of the Thread class and Runnable interface, which return void; a class cannot implement both the Runnable and PrivilegedAction interfaces.
The PrivilegedActionException class is a standard exception, so you must always be prepared to catch it when using the doPrivileged() method. If the embedded run() method does throw an exception, that exception will be wrapped into the PrivilegedActionException, where it may be retrieved with this call:
Return the exception that was originally thrown to cause the PrivilegedActionException to be thrown.
Let's see how all of this might work with our network monitor example:
public class NetworkMonitor { public NetworkMonitor() { try { class doSocket implements PrivilegedExceptionAction { public Object run() throws UnknownHostException, IOException { return new Socket("net.xyz.com", 4000); } }; doSocket ds = new doSocket(); Socket s = (Socket) AccessContoller.doPrivileged(ds); } catch (PrivilegedActionException pae) { Exception e = pae.getException(); if (e instanceof UnknownHostException) { // process host exception } else if (e instanceof IOException { // process IOException } else { // e must be a runtime exception throw (RuntimeException) e; } } } }
Two points are noteworthy here. First, the code that needs to be executed with the privileges of the NetworkMonitor class has been encapsulated into a new class -- the inner doSocket() class.
Second, the exception handling is somewhat new: we must list the exceptions that the socket constructor can throw in the run() method of our embedded class. If either of those exceptions is thrown, it will be encapsulated into a PrivilegedActionException and thrown back to the network monitor, where we can retrieve the actual exception with the getException() method.
Let's examine the effect this call has on the access controller. The access controller begins the same way, by examining the protection domains associated with each method on the stack. But this time, rather than searching every class on the stack, the access controller stops searching the stack when it reaches the class that has called the doPrivileged() method. In the case of Figure 5-3, this means that the access controller does not continue searching the stack after the NetworkMonitor class, so as long as the policy file has a valid entry for the http://network.xyz.com/ code base, the monitor will be able to create its socket.
There's an important (but subtle) distinction to be made here: the doPrivileged() method does not suddenly establish a global permission based on the protection domain of the class that called it. Rather, it specifies a stopping point as the access controller searches the list of protection domains on the stack. In the previous example, we assumed that http://network.xyz.com/ had permission to open the socket. When the access controller searched the protection domains on the stack, it first reached the protection domain associated with http://network.xyz.com/. Since that domain had been marked as the privileged domain, the access controller returned at that point: it never got to the point on the stack where it would have checked (and rejected) the protection domain associated with http://hr.xyz.com/.
Now consider what would happen if the permissions given to these protection domains were reversed; that is, if the http://network.xyz.com/ protection domain is not given permission to open the socket, but the http://hr.xyz.com/ protection domain is. We might be tempted to write the PayrollApp class (knowing that it will have permission to open the socket) like this:
public class PayrollApp { NetworkMonitor nm; public void init() { class doInit implements PrivilegedAction { public void run() { nm = new NetworkMonitor(); } } doInit di = new doInit(); AccessController.doPrivileged(di); } }
When the code within the Socket constructor calls the checkPermission() method, the access controller searches the same stack shown in Figure 5-3. When the access controller reaches the protection domain associated with http://network.xyz.com, it immediately throws an AccessControlException, because that protection domain does not have permission to open sockets. Even though a protection domain lower in the stack does have such a permission, and even though that protection domain has called the doPrivileged() method of the access controller, the operation is rejected when the access controller finds a protection domain that does not have the correct permission assigned to it.
This means that a protection domain can grant privileges to code that has called it, but it cannot grant privileges to code that it calls. This rule permits key operations of the Java virtual machine; if, for example, your nonprivileged class calls the Java API to play an audio clip, the Java API will grant permission to the calling code to write data to the audio device on the machine. When you write your own applications, however, it's important to realize that the permission granting goes only one way.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |