Level: Advanced Carlos Fonseca (cafonseca@us.ibm.com), Software Engineer, IBM
01 Apr 2002 The Java Authentication and Authorization Service (JAAS) is an extension to the Java 2 SDK. Under JAAS, a user or service may be given specific permissions to execute code in a Java class. In this article, software engineer Carlos Fonseca shows you how to extend the JAAS framework for the enterprise. Adding class instance-level authorization and special relationships to the JAAS framework lets you build more dynamic, flexible, and scalable enterprise applications. Share your thoughts on this article with the author and other readers in the discussion forum by clicking Discuss at the top or bottom of the article.
Most Java applications require some sort of class instance-level access control.
For example, the specification for a Web-based, self-service auction application
might have the following requirement:
Any registered (authenticated) user can create an auction but only
the user who created the auction may modify it.
This means that any user can execute the code written to create an
Auction class instance, but only the user who owns
that instance may execute code designed to modify it. Usually, the
user who created the Auction instance will be the owner.
This is referred to as the class instance owner relationship.
Another requirement for the application could be:
Any user can create a bid for an auction and the owner of the auction may
accept or reject any bid.
Again, any user can execute the code written to create an instance of
a Bid class, but only a user who owns that instance will be
given permission to modify it. Furthermore, the owner of the Auction
class instance must be able to modify the acceptance flag in the related Bid
class instances. This means there is a relationship, known as a special relationship, between
the Auction instance and the corresponding Bid
instances.
Unfortunately, the Java Authentication and Authorization Service (JAAS), which is part
of the Java 2 platform, does not allow for class instance-level access control or special
relationships. In this article, we will extend the JAAS framework to include both.
The driving motivation behind this extension is to allow us to separate access control into a
generalized framework that uses policies based on ownership and special relationships.
These policies may then be changed by an administrator over the lifespan of an application.
Before we dive into extending the JAAS framework,
we'll review the Java 2 platform's access control mechanisms. We'll
discuss the use of policy files and permissions, and also talk about
the relationship between the SecurityManager and
the AccessController.
Access control in the Java 2 platform
Under the Java 2 platform, all code, whether it is local or remote, can be controlled by a policy.
A policy is defined by a set of permissions for code in various locations, by
various signers, or by both. A permission permits access to a resource; it
is defined by a name and may be associated with certain actions.
The abstract class
java.security.Policy is used to represent a security policy for
an application. The default implementation is provided by the
sun.security.provider.PolicyFile, in which the policies are
defined in a file. Listing 1 is an example of a typical policy file:
Listing 1. A typical policy file
// Grant these permissions to code loaded from a sample.jar file
// in the C drive and if it is signed by XYZ
grant codebase "file:/C:/sample.jar", signedby "XYZ" {
// Allow socket actions to any host using port 8080
permission java.net.SocketPermission "*:8080", "accept, connect,
listen, resolve";
// Allows file access (read, write, execute, delete) in
// the users home directory.
Permission java.io.FilePermission "${user.home}/-", "read, write,
execute, delete";
};
|
SecurityManager versus AccessController
In the standard JDK distribution, the mechanism to control access to code source is turned
off by default. Prior to the Java 2 platform, access to code source was managed by the
SecurityManager class. The
SecurityManager is enabled by the
java.security.manager system property, as shown here:
java -Djava.security.manager
|
Under the Java 2 platform, an application can be set to use the java.lang.SecurityManager class or the java.security.AccessController class to manage sensitive
operations. The AccessController is new in the Java 2
platform. For backward compatibility, the SecurityManager class still exists but defers its decisions
to the AccessController class. Both the SecurityManager and the AccessController use an application's policy file to
determine whether a requested operation will be allowed. Listing 2 shows how the
AccessController processes a SocketPermission request:
Listing 2. Protecting sensitive operations
Public void someMethod() {
Permission permission =
new java.net.SocketPermission("localhost:8080", "connect");
AccessController.checkPermission(permission);
// Sensitive code starts here
Socket s = new Socket("localhost", 8080);
}
|
In this example, we see the AccessController checking the
application's current policy implementation. If any permission
defined in the policy file implies the requested permission, the method will simply return;
otherwise an AccessControlException will be thrown. The check is
actually redundant in this example, because the constructor for the default socket
implementation performs the same check.
In the next section, we'll look more closely at how the AccessController
works with the java.security.Policy implementation to
securely process application requests.
The AccessController at work
A typical checkPermission(Permission p) method call on the
AccessController class might result in the following sequence:
- The
AccessController invokes the
getPermissions(CodeSource codeSource) method of the
java.security.Policy class implementation.
- The
getPermissions(CodeSource codeSource) method returns a
PermissionCollection class instance, which represents
a collection of the same type of permissions.
- The
AccessController calls the implies(Permission p) method of the PermissionCollection class.
- In turn, the
PermissionCollection calls the implies(Permission p) method of the individual Permission objects contained in the collection. These
methods return true if the current permission object in the collection implies
the specified permission, false if it does not.
Now, let's look at the important elements of this control-access sequence
in more detail.
The PermissionCollection class
Most permission class types have a corresponding PermissionCollection class. An instance of such a collection may be created by calling the newPermissionCollection() method defined by the Permission subclass implementation. The getPermissions() method of the java.security.Policy class implementation may also return
a Permissions class instance, a subclass of PermissionCollection. This class represents a collection of
different types of permission objects organized by PermissionCollection. The implies(Permission p) method of the Permissions class
may call the implies(Permission p) method of the
individual PermissionCollection classes.
CodeSource and the ProtectionDomain class
The combination of permissions and CodeSource (code
location and the certificates that were used to verify the signed code) are
encapsulated in the ProtectionDomain class. Class
instances that have the same permissions and the same CodeSource are placed in the same domain. Classes with the
same permissions but different CodeSources are placed
in different domains. A class can only belong to one ProtectionDomain. To obtain the ProtectionDomain for an object, use the getProtectionDomain() method defined in the java.lang.Class class.
Permissions
Assigning permissions to CodeSources does not necessarily mean the
implied operations will be allowed. Each class in the calling stack must have the required
permissions for the operations to complete successfully. In other words,
if you assign a java.io.FilePermission to class B, and class B is
called by class A, then class A must also have the same permission or a permission that implies
java.io.FilePermission.
On the other hand, a calling class may need temporary permissions to complete an
operation in another class that has those permissions. For example, a
class load from another location may not be trusted for access to the local filesystem. Classes
loaded locally, however, are given read permissions for a certain directory. These classes may
implement the PrivilegedAction interface to give the calling classes permission
to complete the specified operations. The check of the calling stack stops when it encounters
a PrivilegedAction instance, effectively granting all subsequent class calls the
required permission to perform the specified operation.
Working with JAAS
As suggested by its name, JAAS consists of two primary components: authentication and
authorization. We're concerned mostly with extending the authorization component of
JAAS, but we'll start with a brief overview of JAAS authentication, followed
by a look at a simple JAAS authorization operation.
User authentication in JAAS
JAAS augments the access control security model defined in Java 2 by adding
subject-based policies. Permission is granted not only based on the CodeSource but also on the user executing the code. Obviously, for this model to work each user must be authenticated.
JAAS's authentication mechanism is built on a set of pluggable login modules. The JAAS
distribution contains several LoginModule
implementations. LoginModules may be used to prompt
the user for a userid and password. The LoginContext
class uses a configuration file to determine which LoginModule to use to authenticate the user. This configuration may be specified through the system property java.security.auth.login.config. An example configuration
is:
java -Djava.security.auth.login.config=login.conf
|
And here's how a login configuration file might look:
Example {
com.ibm.resource.security.auth.LoginModuleExample required
debug=true userFile="users.xml" groupFile="groups.xml";
};
|
Know your principles
The Subject class is used to encapsulate the credentials of an
authenticated entity such as a user. A Subject may have a grouping of identities called principals. For example, if the Subject is a user, the user's name and social security number may be some of the
Subject's identities, or principals. Principals are associated
with the identity names.
The Principal implementation class along with its name is
specified in the JAAS policy file. The default JAAS implementation uses a policy
file similar to that of the Java 2 implementation, except that each grant statement must be associated with at least one principal. The javax.security.auth.Policy abstract class is used to represent the JAAS security policy. The default implementation is provided by the com.sun.security.auth.PolicyFile in which the policies are defined in a file. Listing 3 is an example of a JAAS policy file:
Listing 3. Example JAAS policy file
// Example grant entry
grant codeBase "file:/C:/sample.jar", signedby "XYZ",
principal com.ibm.resource.security.auth.PrincipalExample "admin" {
// Allow socket actions to any host using port 8080
permission java.net.SocketPermission
"*:8080", "accept, connect, listen, resolve";
// Allows file access (read, write, execute, delete) in
// the user's home directory.
Permission java.io.FilePermission
"${user.home}/-", "read, write, execute, delete";
};
|
This example is similar to the standard Java 2 policy file shown in Listing 1. In fact, the only difference is the principal statement, which states that only subjects (users) that have the
specified principal and principal name are granted the specified permissions.
Again, a system property, java.security.auth.policy, is used to indicate where the JAAS policy file resides, as shown below:
java -Djava.security.auth.policy=policy.jaas
|
The Subject class contains several methods to perform work
as a particular subject; these methods are as follows:
public static Object
doAs(Subject subject, java.security.PrivilegedAction action)
public static Object
doAs(Subject subject, java.security.PrivilegedAction action)
throws java.security.PrivilegedActionException
|
Note that sensitive code is protected in the same way described in the
Java 2 CodeSource Access Control overview. See the Resources section to learn more about code source access control and authentication in JAAS.
Authorization in JAAS
Listing 4 shows the results of an authorization request using the JAAS policy file shown in
Listing 3. Assume that a SecurityManager has
been installed and a Subject with a
com.ibm.resource.security.auth.PrincipalExample principal
named "admin" has been authenticated by the loginContext.
Listing 4. A simple authorization request
public class JaasExample {
public static void main(String[] args) {
...
// where authenticatedUser is a Subject with
// a PrincipalExample named admin.
Subject.doAs(authenticatedUser, new JaasExampleAction());
...
}
}
public class JaasExampleAction implements PrivilegedAction {
public Object run() {
FileWriter fw = new FileWriter("hi.txt");
fw.write("Hello, World!");
fw.close();
}
}
|
Here the sensitive code is encapsulated in the JaasExampleAction class. Also note that the calling classes do not require the permissions granted to the JaasExampleAction class code source, because it implements a PrivilegedAction.
Extending JAAS
Most applications have custom logic that authorizes users to perform an operation not only on
a class, but also on an instance of that class. Such authorization is usually based on a
relationship between the user and the instance. This is where JAAS falls a little short.
Fortunately, however, JAAS was designed so that it can be extended. With a little bit of work,
we can extend JAAS to include a generalized class instance-level authorization framework.
As I stated at the beginning of this article, the abstract class javax.security.auth.Policy is used to represent the JAAS security policy. Its default implementation is provided by the com.sun.security.auth.PolicyFile class. The PolicyFile class reads the policies from a JAAS-formatted file like the one shown in Listing 3.
We need to add one thing to this file to extend the policy definition for class instance-level authorization: an optional relationship parameter associated with a permission statement.
The default JAAS permission statement has the following format:
permission <permission implementation class> [name], [actions];
|
We add the optional relationship parameter to the end of this
permission statement to complete the policy definition. Here's the
format for the new permission statement:
permission <permission implementation class>
[name], [actions], [relationship];
|
The most important thing to note when extending JAAS for class instance-level authorization is that the permission implementation class must have a three-parameter constructor. The first parameter is for the name, the second for the actions, and the last for the relationship.
Parsing the new file format
Since the format of the file has changed, a new javax.security.auth.Policy subclass is needed to parse the file.
For simplicity, our example employs the new javax.security.auth.Policy subclass, com.ibm.resource.security.auth.XMLPolicyFile, to read the policies from an XML file. In a real-world enterprise application, a relational database would be better suited to handle this task.
The easiest way to replace the default JAAS access control policy implementation with the XMLPolicyFile class is by adding the auth.policy.provider=com.ibm.resource.security.auth.XMLPolicyFile entry to the java.security properties file. The java.security properties file is located in the lib/security directory of the Java 2 platform runtime. Listing 5 is a sample XML policy file used with the XMLPolicyFile class:
Listing 5. An XML policy file
<?xml version="1.0"?>
<policy>
<grant codebase="file:/D:/sample_actions.jar">
<principal classname=
"com.ibm.resource.security.auth.PrincipalExample" name="users">
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="create" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="read" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="write"
relationship="owner" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="create" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="read" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="write"
relationship="owner" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="accept"
relationship="actionOwner" />
</principal>
</grant>
</policy>
|
In this example policy file, any user (Subject) associated with the PrincipalExample named users can create and read an Action.class instance. However, only the user that created the instance can update (write) it. This is defined by the third permission element, which contains the relationship attribute value of owner. The same holds true for the Bid.class instances, except that the owner of the corresponding Auction.class instance can change the bid acceptance flag.
The Resource interface
Classes that require class instance-level access control must implement the Resource interface. The getOwner() method of the interface returns the owner of the class instance. The fulfills(Subject subject, String relationship) method is used for dealing with special relationships. In addition, these classes use the com.ibm.resource.security.auth.ResourcePermission class to protect sensitive code. For example, the Auction class has the following constructor:
public Auction() {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "create");
AccessController.checkPermission(permission);
}
|
The owner relationship
The implies(Permission p) method of the ResourcePermission class is the key to this framework. The implies() method compares the name and actions properties for equality. If a relationship is defined, then the class instance (Resource) being protected must be passed into the ResourcePermission constructor. The ResourcePermission class understands the owner relationship. It compares the owner of the class instance with the subject
(user) executing the code. Special relationships are delegated to the fulfills() method of the class being protected.
For example, in the XML policy file shown in Listing 5, only the owner of the
Auction class instance can update (write) the file. The
setter methods of the class use the protection code shown in Listing 6:
Listing 6. The implies(Permission) method at work
public void setName(String newName) {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "write", this);
AccessController.checkPermission(permission);
// sensitive code
this.name = newName;
}
|
The this reference that is passed into the ResourcePermission constructor represents the Resource interface that the Auction class implements. Because the relationship listed in the policy file is owner, the ResourcePermission class uses this reference to check if the current Subject (user) has a principal that matches the owner of the instance. If another relationship is specified, then the ResourcePermission class calls the fulfills(Subject subject, String relationship) method of the Auction class. It is up to the Resource implementing class to provide the logic in the fulfills() method.
The Bid class listed in the XML policy file has the methods shown in Listing 7 (assume the Bid class instance has a reference, auction, to the corresponding Auction class instance).
Listing 7. Handling special relationships
public void setAccepted(boolean flag) {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "accept", this);
AccessController.checkPermission(permission);
// sensitive code
this.accepted = flag;
}
public boolean fulfills(Subject user, String relationship) {
if( relationship.equalsIgnoreCase("auctionOwner") ) {
String auctionOwner = auction.getOwner();
Iterator principalIterator = user.getPrincipals().iterator();
while(principalIterator.hasNext()) {
Principal principal = (Principal) principalIterator.next();
if( principal.getName().equals(auctionOwner) )
return true;
}
}
return false;
}
|
The relationship string passed in to the fulfills() method is the relationship listed in the policy file. In this case, the "auctionOwner" string was used.
By default, the XMLPolicyFile class looks for a
file named ResourcePolicy.xml in the current working
directory. The system property, com.ibm.resource.security.auth.policy, may be used to specify a different file name and location.
A working example
To pull all of this information together, we'll run a simple command-line example. The example program contains three jar files:
- resourceSecurity.jar
- example.jar
- exampleActions.jar
The resourceSecurity.jar file contains the JAAS extension framework that allows instance-level access control. It also contains a LoginModuleExample class that reads user authentication information from an XML file. Userids and passwords are stored in the users.xml file. User groups are stored in the groups.xml file. See the Resources section for more information on LoginModuleExample.
The example contains four additional files:
- login.conf
- policy
- resourcePolicy.xml
- run.bat
Be sure you update the file paths in the run.bat, policy, and resourcePolicy.xml
files before attempting to run the example. By default all passwords are "passw0rd".
How the example works
The example program prompts for a userid and password. It checks the supplied userid and
password with the entries in the users.xml file. After the user is authenticated, the program tries to create a UserProfile class instance, modify it, and read from it. By default the owner of the UserProfile class is Jane
(jane). When Jane logs in, all three operations are successful. When John (john) logs in, only the create operation is successful. When Lou (lou), Jane's manager, logs in, only the first and last operations are successful. When the system administrator (admin) logs in, all operations are successful. Of course, all this will only be true if the supplied ResourcePolicy.xml file has not been modified.
Example setup
The following setup instructions assume you are using JDK 1.3 and have extracted the files to
the d:\JaasExample directory. You will save some work by extracting the files to this directory;
otherwise you will have to modify the policy and the ResourceSecurity.xml policy files with the
correct path names.
Here's what you need to do to run the example:
- Download the source files for this example.
- Copy the jaas.jar and the jaasmod.jar to your JDK jre\lib\ext directory
(i.e., D:\JDK1.3\jre\lib\ext).
- Add the following string to the end of the java.security file located in
JDK's jre\lib\security directory (i.e., D:\JDK1.3\jre\lib\security):
auth.policy.provider=com.ibm.resource.security.auth.XMLPolicyFile.
- Execute the run.bat file.
Conclusion
Class instance-level authorization separates access control into a generalized framework that uses policies based on ownership and special relationships. These policies may then be changed by an administrator over the lifespan of an application. Extending the JAAS in this
way decreases the chances that you or another programmer will have to rewrite the code as the business rules change over the lifespan of the application.
The special-relationship concept can be further extended by abstracting the relationship string to a class. Instead of calling the fulfills(Subject user, String relationship) method of the Resource implementation class, you would simply call a new fulfills(Subject user, Resource resource) method defined in the Relationship implementation class. This would allow many Resource implementation classes to use the same relationship logic.
Download | Name | Size | Download method |
|---|
| j-jaas/standaloneExample.zip | | HTTP |
Resources - Download the source code for the examples used in this article.
- To learn more about the Java Authentication and Authorization Service, see the JAAS 1.0 Developers Guide.
- The Java 2 API Documentation is another excellent resource for learning about extensions to the Java 2 platform.
- You can learn more about the Java platform's security model by visiting Sun
Microsystems's Java tutorial trail, Security in
Java 2 SDK 1.2, which includes a lesson on policy files.
- For an in-depth introduction to Java security, see Scott Oaks's Java Security, 2nd Edition
(O'Reilly & Associates, May 2001).
- For more hands-on learning with JAAS, see Thomas Owusu's "Enhance Java GSSAPI
with a login interface using JAAS" (developerWorks, November 2001).
- If you want to gain insight into IBM's historic role in the evolution of Java security,
see the article, "The evolution of Java security," in which IBM engineers discuss the pros and cons of the JDK 1.2 security model.
- You'll find hundreds of articles about every aspect of Java programming in
the IBM developerWorks Java technology zone.
About the author  | |  |
Currently a software engineer for IBM Research, Carlos Fonseca has been developing software professionally for 10 years. He started programming in C and Visual Basic, then moved on to C++ programming. For the last six years Carlos has focused on object-oriented design and development using the Java platform, lending his expertise to projects that range from Swing GUI stand-alone applications to server-side applications using J2EE. When not developing software professionally, Carlos also enjoys programming as a hobby. You can contact Carlos at cafonseca@us.ibm.com. |
Rate this page
|