CONTENTS | PREV | NEXT

4 Access Control Mechanisms and Algorithms


4.1 java.security.ProtectionDomain

The ProtectionDomain class encapsulates the characteristics of a domain. Such a domain encloses a set of classes whose instances are granted a set of permissions when being executed on behalf of a given set of Principals.

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).


4.2 java.security.AccessController

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 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.


4.2.1 Algorithm for Checking Permissions

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.

Illustration of access control

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:
  • In an "eager evaluation" implementation, whenever a thread enters a new protection domain or exits from one, the set of effective permissions is updated dynamically.
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.
  • In a "lazy evaluation" implementation, whenever permission checking is requested, the thread state (as reflected by the current state, including the current thread's call stack or its equivalent) is examined and a decision is reached to either deny or grant the particular access requested.
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 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;
 };

4.2.2 Handling Privileges

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, 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.


4.3 Inheritence of Access Control Context

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 when 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.

4.4 java.security.AccessControlContext

Recall that the AccessController 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);


CONTENTS | PREV | NEXT

Oracle and/or its affiliates Copyright © 1993, 2015, Oracle and/or its affiliates. All rights reserved.

微信小程序

微信扫一扫体验

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部