CONTENTS | PREV | NEXT | Java Security Architecture |
This class represents a unit of protection within the Java application environment, and is typically associated with a concept of "principal," where a principal is an entity in the computer system to which permissions (and as a result, accountability) are granted.A domain conceptually encloses a set of classes whose instances are granted the same set of permissions. Currently, a domain is uniquely identified by a CodeSource, which encapsulates two characteristics of the code running inside the domain: the codebase (java.net.URL), and a set of certificates (of type java.security.cert.Certificate) for public keys that correspond to the private keys that signed all code in this domain. Thus, classes signed by the same keys and from the same URL are placed in the same domain.
A domain also encompasses the permissions granted to code in the domain, as determined by the security policy currently in effect.
Classes that have the same permissions but are from different code sources belong to different domains.
A class belongs to one and only one ProtectionDomain.
Note that currently in JDK 1.2, protection domains are created "on demand" as a result of class loading. The
getProtectionDomain
method in java.lang.Class can be used to look up the protection domain that is associated with a given class. Note that one must have the appropriate permission (the RuntimePermission "getProtectionDomain") to successfully invoke this method.Today all code shipped as part of the JDK is considered system code and run inside the unique system domain. Each applet or application runs in its appropriate domain, determined by its code source.
It is possible to ensure that objects in any non-system domain cannot automatically discover objects in another non-system domain. This partition can be achieved by careful class resolution and loading, for example, using different classloaders for different domains. However, SecureClassLoader (or its subclasses) can, at its choice, load classes from different domains, thus allowing these classes to co-exist within the same name space (as partitioned by a classloader).
The AccessController class is used for three purposes, each of which is described in further detail in sections below:
- to decide whether an access to a critical system resource is to be allowed or denied, based on the security policy currently in effect,
- to mark code as being "privileged", thus affecting subsequent access determinations, and
- to obtain a "snapshot" of the current calling context so access-control decisions from a different context can be made with respect to the saved context.
Any code that controls access to system resources should invoke AccessController methods if it wishes to use the specific security model and access control algorithm utilized by these methods. If, on the other hand, the application wishes to defer the security model to that of the SecurityManager installed at runtime, then it should instead invoke corresponding methods in the SecurityManager class.For example, the typical way to invoke access control has been the following code (taken from an earlier version of JDK):
ClassLoader loader = this.getClass().getClassLoader(); if (loader != null) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead("path/file"); } }
Under the new architecture, the check typically should be invoked whether or not there is a classloader associated with a calling class. It could be simply, for example:
FilePermission perm = new FilePermission("path/file", "read"); AccessController.checkPermission(perm);
The AccessControllercheckPermission
method examines the current execution context and makes the right decision as to whether or not the requested access is allowed. If it is, this check returns quietly. Otherwise, an AccessControlException (a subclass of java.lang.SecurityException) is thrown.Note that there are (legacy) cases, for example, in some browsers, where whether there is a SecurityManager installed signifies one or the other security state that may result in different actions being taken. For backward compatibility, the
checkPermission
method on SecurityManager can be used.
SecurityManager security = System.getSecurityManager(); if (security != null) { FilePermission perm = new FilePermission("path/file", "read"); security.checkPermission(perm); }
We currently do not change this aspect of the SecurityManager usage, but would encourage application developers to use new techniques introduced in this new version of the JDK in their future programming when the built-in access control algorithm is appropriate.The default behavior of the SecurityManager
checkPermission
method is actually to call the AccessControllercheckPermission
method. A different SecurityManager implementation may implement its own security management approach, possibly including the addition of further constraints used in determining whether or not an access is permitted.
Suppose access control checking occurs in a thread of computation that has a chain of multiple callers (think of this as multiple method calls that cross the protection domain boundaries), as illustrated in the next figure.When the
checkPermission
method of the AccessController is invoked by the most recent caller (e.g., a method in the File class), the basic algorithm for deciding whether to allow or deny the requested access is as follows.
If any caller in the call chain does not have the requested permission, AccessControlException is thrown, unless the following is true -- a caller whose domain is granted the said permission has been marked as "privileged" (see the next section) and all parties subsequently called by this caller (directly or indirectly) all have the said permission.There are obviously two implementation strategies:
The benefit is that checking whether a permission is allowed is simplified and can be faster in many cases. The disadvantage is that, because permission checking occurs much less frequently than cross-domain calls, a large percentage of permission updates are likely to be useless effort.
One potential downside of this approach is performance penalty at permission checking time, although this penalty would have been incurred anyway in the "eager evaluation" approach (albeit at earlier times and spread out among each cross-domain call). Our implementation so far has yielded acceptable performance, so we feel that lazy evaluation is the most economical approach overall.Therefore, the algorithm for checking permissions is currently implemented as "lazy evaluation". Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller m. Then caller m invoked the
checkPermission
method. The basic algorithmcheckPermission
uses to determine whether access is granted or denied is the following (see subsequent sections for refinements):
i = m; while (i > 0) { if (caller i's domain does not have the permission) throw AccessControlException else if (caller i is marked as privileged) return; i = i - 1; };
A new, static method in the AccessController class allows code in a class instance to inform the AccessController that a body of its code is "privileged" in that it is solely responsible for requesting access to its available resources, no matter what code caused it to do so.That is, a caller can be marked as being "privileged" when it calls the
doPrivileged
method. When making access control decisions, thecheckPermission
method stops checking if it reaches a caller that was marked as "privileged" via adoPrivileged
call without a context argument (see a subsequent section for information about a context argument). If that caller's domain has the specified permission, no further checking is done andcheckPermission
returns quietly, indicating that the requested access is allowed. If that domain does not have the specified permission, an exception is thrown, as usual.The normal use of the "privileged" feature is as follows:
If you don't need to return a value from within the "privileged" block, do the following:
somemethod() { ...normal code here... AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // privileged code goes here, for example: System.loadLibrary("awt"); return null; // nothing to return } }); ...normal code here... }
PrivilegedAction is an interface with a single method, namedrun
, that returns an Object. The above example shows creation of an anonymous inner class implementing that interface; a concrete implementation of therun
method is supplied. When the call todoPrivileged
is made, an instance of the PrivilegedAction implementation is passed to it. ThedoPrivileged
method calls therun
method from the PrivilegedAction implementation after enabling privileges, and returns therun
method's return value as thedoPrivileged
return value, which is ignored in this example.(For more information about inner classes, see the Inner Classes Specification at
http://java.sun.com/products/jdk/1.1/docs/guide/innerclasses/spec/innerclasses.doc.html
or pages of the More Features of the Java Language trail of the Java Tutorial, includinghttp://java.sun.com/docs/books/tutorial/java/more/nested.html
andhttp://java.sun.com/docs/books/tutorial/java/more/innerclasses.html
.)If you need to return a value, you can do something like the following:
somemethod() { ...normal code here... String user = (String) AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return System.getProperty("user.name"); } } ); ...normal code here... }
If the action performed in yourrun
method could throw a "checked" exception (one listed in thethrows
clause of a method), then you need to use the PrivilegedExceptionAction interface instead of the PrivilegedAction interface:
somemethod() throws FileNotFoundException { ...normal code here... try { FileInputStream fis = (FileInputStream) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws FileNotFoundException { return new FileInputStream("someFile"); } } ); } catch (PrivilegedActionException e) { // e.getException() should be an instance of // FileNotFoundException, // as only "checked" exceptions will be "wrapped" in a // <code>PrivilegedActionException</code>. throw (FileNotFoundException) e.getException(); } ...normal code here... }
Some important points about being privileged: Firstly, this concept only exists within a single thread. As soon as the privileged code completes, the privilege is guaranteed to be erased or revoked.Secondly, in this example, the body of code in the
run
method is privileged. However, if it calls less trustworthy code that is less privileged, that code will not gain any privileges as a result; a permission is only granted if the privileged code has the permission and so do all the subsequent callers in the call chain up to thecheckPermission
call.For more information about marking code as "privileged," see
http://java.sun.com/products/jdk/1.2/docs/guide/security/doprivileged.html
.
When a thread creates a new thread, a new stack is created. If the current security context was not retained when this new thread was created, then whenAccessController.checkPermission
was called inside the new thread, a security decision would be made based solely upon the new thread's context, not taking into consideration that of the parent thread.This clean stack issue would not be a security problem per se, but it would make the writing of secure code, and especially system code, more prone to subtle errors. For example, a non-expert developer might assume, quite reasonably, that a child thread (e.g., one that does not involve untrusted code) inherits the same security context from the parent thread (e.g., one that involves untrusted code). This would cause unintended security holes when accessing controlled resources from inside the new thread (and then passing the resources along to less trusted code), if the parent context was not in fact saved.
Thus, when a new thread is created, we actually ensure (via thread creation and other code) that it automatically inherits the parent thread's security context at the time of creation of the child thread, in such a way that subsequent
checkPermission
calls in the child thread will take into consideration the inherited parent context.In other words, the logical thread context is expanded to include both the parent context (in the form of an AccessControlContext, described in the next section) and the current context, and the algorithm for checking permissions is expanded to the following. (Recall there are m callers up to the call to
checkPermission
, and see the next section for information about the AccessControlContextcheckPermission
method.)
i = m; while (i > 0) { if (caller i's domain does not have the permission) throw AccessControlException else if (caller i is marked as privileged) return; i = i - 1; }; // Next, check the context inherited when // the thread was created. Whenever a new thread is created, the // AccessControlContext at that time is // stored and associated with the new thread, as the "inherited" // context. inheritedContext.checkPermission(permission);
Note that this inheritance is transitive so that, for example, a grandchild inherits both from the parent and the grandparent. Also note that the inherited context snapshot is taken when the new child is created, and not when the child is first run. There is no public API change for the inheritance feature.
Recall that the AccessControllercheckPermission
method performs security checks within the context of the current execution thread (including the inherited context). A difficulty arises when such a security check can only be done in a different context. That is, sometimes a security check that should be made within a given context will actually need to be done from within a different context. For example, when one thread posts an event to another thread, the second thread serving the requesting event would not have the proper context to complete access control, if the service requests access to controller resources.To address this issue, we provide the AccessController
getContext
method and AccessControlContext class. ThegetContext
method takes a "snapshot" of the current calling context, and places it in an AccessControlContext object, which it returns. A sample call is the following:
AccessControlContext acc = AccessController.getContext();
This context captures relevant information so that an access control decision can be made by checking, from within a different context, against this context information. For example, one thread can post a request event to a second thread, while also supplying this context information. AccessControlContext itself has acheckPermission
method that makes access decisions based on the context it encapsulates, rather than that of the current execution thread. Thus, the second thread can perform an appropriate security check if necessary by invoking the following:
acc.checkPermission(permission);
The above method call is equivalent to performing the same security check in the context of the first thread, even though it is done in the second thread.There are also times where one or more permissions must be checked against an access control context, but it is unclear a priori which permissions are to be checked. In these cases you can use the
doPrivileged
method that takes a context:
somemethod() { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { // Code goes here. Any permission checks from // this point forward require both the current // context and the snapshot's context to have // the desired permission. } }, acc); ...normal code here...
Now the complete algorithm utilized by the AccessControllercheckPermission
method can be given. Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller m. Then caller m invoked thecheckPermission
method. The algorithmcheckPermission
uses to determine whether access is granted or denied is the following
i = m; while (i > 0) { if (caller i's domain does not have the permission) throw AccessControlException else if (caller i is marked as privileged) { if (a context was specified in the call to doPrivileged) context.checkPermission(permission); return; } i = i - 1; }; // Next, check the context inherited when // the thread was created. Whenever a new thread is created, the // AccessControlContext at that time is // stored and associated with the new thread, as the "inherited" // context. inheritedContext.checkPermission(permission);