In a previous article, we took a first look at the authentication aspect of JAAS; now we'll look at authorization.
Before we start, it's important to understand the basics of the Java security model. By default applications run (get interpreted) without a security manager, which means that there are no restrictions in what the code is allowed to do. There are a number of issues in giving untrusted programs full control of the JVM, as the following examples show.
Applets are downloaded from a remote site, but run on the client. If we give the applet code full permissions then it is free to open a file and start appending junk data to it, until finally that leads to a disk full errors. Or it can transfer confidential files elsewhere over the network.
Denial of service attacks are another example. Suppose a web hosting company provides web application server space for lease. If the server does not run with a security manager then we can easily have a servlet call System.exit(0), which would bring the server down any time we access the servlet.
From this discussion it should be clear why security functions are an integral part of Java. This is a challenge for any portable language (this discussion also applies to Flash/Flex and JavaScript), so the Java language designers have taken great care in handling the security aspects. So how does a security manager help prevent these kinds of attacks?
First, let's have a look at Java permissions and policy files. The developer generally documents the permissions required by his code to work properly (when running on a JVM with a security manager enabled). It is quite likely that the freshly developed code will run on someone else's JVM. So the person running the program must to go through the documentation and grant the code with the required permissions (assuming that a security manager is used, which we shall assume). Only then will the program be able to run. If the program is run without granting proper permissions (using the policy file) and a security manager enabled, then a variety of scary-looking security exceptions will be thrown. The policy file is used to grant the required permissions to a piece of code. This is the core of JAAS authorization, and we will dig deeper into it later in this article.
What is a Principal?
A Principal is a representation of a subject in our system. It's basically an implementation of Principal interface. E.g., one could have a DepartmentName principal which represents the department of an authenticated subject. So when the subject (e.g., an employee) is authenticated, one might want to create a department principal which represents the respective department and associate it that with that subject. This is the core of JAAS authorization. One can grant various permissions to principals using a policy file. This is described in the next section.
But what if we want our code to be run only by a certain set of users? In this case authorization enters the picture. JAAS authorization combines the permission domain of a piece of code along with the permissions of a user before running the code. The previous section explained the permission domain of the code, but what is the permissions domain of the user? How do we grant permissions to individual users? This is done using a principal. We grant principals with various permissions using the policy file (this is the same policy file which we used to grant various permissions to our code). After the user is authenticated, we populate the Subject with the principal. JAAS thus extends code based security to user based security. Now only authenticated users having the required principal can invoke a particular code/operation (because the principal in turn has the required permissions to run that operation). But where is the actual code that associates the subject with the access control context and runs the code? For that, we need to run our code in the following manner, which takes care of associating the subject with the access control context.
For that we use the doAsPriviledged method of the Subject, and pass the authenticated Subject as the first parameter, and as the second parameter the PrivilegedAction, which is the code that should be run in the context of the newly created protection domain. The third parameter is null, as we want an empty context to be used:
Subject.doAsPrivileged (subject, new PrivilegedAction() {
public Object run() {
System.setProperty("file.encoding", "ASCII");
return null;
}
}, null);
To understand the above piece of code, let's examine the basics of Java security again. Firstly, the AccessController class, which performs security checks. Java has a method stack (outside the heap space) where the currently executing methods live. Each stack can be considered as an independent thread of execution. The AccessController checks whether the current stack of methods has all the required permissions to invoke an operation or not. Some of its important methods are:
static void checkPermission (Permission)
Used to determine whether the code has the required permissions mentioned in the parameter. If it does, then the method returns quietly, otherwise, it throws a runtime exception. Not only should the current call have the permission but the callers beneath it should also have the required permission for this method to succeed.
public static Object doPrivileged (PrivilegedAction)
A caller can be marked as Privileged, which indicates that one does not want the permission check to proceed further down the stack. In that case -if the caller has the required permission- then it returns quietly without any exception. It will not go further down the stack to find out whether all the methods beneath this method in the stack have the required permissions as well. There's also an overloaded method that takes a PrivilegedExceptionAction instead of a PrivilegedAction. This can be used in case the operation throws a checked exception.
I would recommend to read the class level javadoc for the AccessController class. It includes a good example of how to use it.
Now lets take a look at the authorization related methods defined in Subject. There are two sets of interesting methods - doAs and doAsPrivileged. The main purpose of these is to associate the authenticated subject with the the current AccessControlContext. The only difference between these sets of methods is that, in the latter, you can provide a AccessControlContext to use, while the former uses the current thread's AccessControlContext. If the AccessControlContext is null when calling doAsPrivileged, then it doesn't use the current thread's context, instead it creates an empty context (which has not protection domain) and merges it with the subject. So eventually it's only the protection domain of the subject, which in turn is used for the authorization.
Running the examples
Make sure that jaas.config, policy.config and the jaas-example.jar file are in the same directory. (There are also Ant targets named runCase1, runCase2, runcase3 and runCase4 that will execute these test program with the specified parameters.) The complete, ready-to-run code can be downloaded here.
The example does nothing fancier than changing the default encoding that is used for writing files. The Java code for that is System.setProperty("file.encoding", "ASCII"), and you can find it in the TestJaasAuthorization class. It's a simple example, but since this property has an impact on a wide range of I/O actions, it's protection-worthy nonetheless.
The login module for all examples is written so that authentication is successful if the username matches the password (a bit simplistic, but since the subject here is authorization -and not secure authentication- it's quite sufficient) and a principal with the username can be be created and attached to the already authenticated subject. Then the authorization is performed. You can run the example by invoking ant runCase1 which executes the following command:
java -Duser=rahul -Dpass=rahul -Djava.security.auth.login.config=jaas.config -Djava.security.manager -Djava.security.policy=policy-rahul-only.config -jar jaas-example.jar
In this case the authentication would succeed and principal RanchPrincipal would be attached to the authenticated subject.Through the policy file we have granted RanchPrincipal with the required permission only to user "rahul". The code runs properly to the end and will successfully change the value of the file.encoding property to ASCII regardless of the current value.
ant runCase2 executes the following command:
java -Duser=bob -Dpass=bob -Djava.security.auth.login.config=jaas.config -Djava.security.manager -Djava.security.policy=policy-rahul-only.config -jar jaas-example.jar
While user bob is successfully authenticated (username and password are identical), we're still using the policy config file that gives the critical permission to user "rahul" only. So in this case the authentication would succeed, but the authorization would fail and the subject will not be able to invoke the operation.
The third case fixes that. Again, the authentication succeeds, and the principal RanchPrincipal is with parameter "bob" is created and attached to the authenticated subject. Since we're now using a different policy config file (which gives the permission to all authenticated users – that's what the "*" after RanchPrincipal means), the code proceeds smoothly.
java -Duser=bob -Dpass=bob -Djava.security.auth.login.config=jaas.config -Djava.security.manager -Djava.security.policy=policy-everybody.config -jar jaas-example.jar
The fourth case demonstrates authentication failure. In this case the subject will not be populated with any Principal. In fact, a null subject would be returned from the LoginContext. And since only authenticated users are allowed to run the code, the execution is denied. This is just to make sure that authentication still works (without which proper handling of authorization wouldn't be worth much):
java -Duser=bob -Dpass=rahul -Djava.security.auth.login.config=jaas.config -Djava.security.manager -Djava.security.policy=policy-everybody.config -jar jaas-example.jar