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 in the Java Tutorials.
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);