CONTENTS | PREV | NEXT |
A ProtectionDomain is constructed with a CodeSource, a ClassLoader, an array of Principals, and a collection of Permissions. The CodeSource encapsulates the codebase (java.net.URL) for all classes in this domain, as well as 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. The Principals represent the user on whose behalf the code is running.
The permissions passed in at ProtectionDomain construction time represent a static set of permissions bound to the domain regardless of the Policy in force. The ProtectionDomain subsequently consults the current policy during each security check to retrieve dynamic permissions granted to the domain.
Classes from different CodeSources, or that are being executed on behalf of different principals, belong to different domains.
Today all code shipped as part of the Java 2 SDK is considered system code and run inside the unique system domain. Each applet or application runs in its appropriate domain, determined by policy.
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).
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 AccessController
checkPermission
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 the Java 2 SDK 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 AccessController checkPermission
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.
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.
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
algorithm checkPermission
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; };
That is, a caller can be marked as
being "privileged" when it calls the doPrivileged
method. When making access control decisions, the
checkPermission
method stops checking if it reaches a
caller that was marked as "privileged" via a
doPrivileged
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 and checkPermission
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, named
run
, that returns an Object.
The above example shows creation of an anonymous inner class
implementing that interface; a concrete implementation of the
run
method is supplied. When the call to
doPrivileged
is made, an instance of the
PrivilegedAction implementation is passed to it. The
doPrivileged
method calls the run
method
from the PrivilegedAction implementation after enabling privileges,
and returns the run
method's return value as the
doPrivileged
return value, which is ignored in this
example.
For more information about inner classes, see Nested Classes and Inner Class Example.
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 your
run
method could throw a "checked" exception (one
listed in the throws
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 the
checkPermission
call.
For more information about marking code as "privileged," see API for Privileged Blocks.
AccessController.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 AccessControlContext
checkPermission
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.
checkPermission
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. The getContext
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 a
checkPermission
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 AccessController
checkPermission
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 the
checkPermission
method. The algorithm
checkPermission
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);