JAAS Authorization Tutorial
This tutorial expands the program and policy file developed in the JAAS Authentication Tutorial tutorial to demonstrate the JAAS authorization component, which ensures the authenticated caller has the access control rights (permissions) required to do subsequent security-sensitive operations. Since the authorization component requires that the user authentication first be completed, please read the JAAS Authentication Tutorial tutorial first if you have not already done so.
The rest of this tutorial consists of the following sections:
- What is JAAS Authorization?
- How is JAAS Authorization Performed?
- The Authorization Tutorial Code
- The Login Configuration File for the JAAS Authorization Tutorial
- The Policy File
- Running the Authorization Tutorial Code
If you want to first see the tutorial code in action, you can skip directly to Running the Authorization Tutorial Code and then go back to the other sections to learn more.
What is JAAS Authorization?
JAAS authorization extends the existing Java security architecture that uses a security policy (see Default Policy Implementation and Policy File Syntax) to specify what access rights are granted to executing code. That architecture is code-centric. That is, the permissions are granted based on code characteristics: where the code is coming from and whether it is digitally signed and if so by whom. We saw an example of this in the sampleacn.policy
file used in the JAAS Authentication Tutorial tutorial. That file contains the following:
grant codebase "file:./SampleAcn.jar" {
permission javax.security.auth.AuthPermission
"createLoginContext.Sample";
};
This grants the code in the SampleAcn.jar
file, located in the current directory, the specified permission. (No signer is specified, so it doesn't matter whether the code is signed or not.)
JAAS authorization augments the existing code-centric access controls with new user-centric access controls. Permissions can be granted based not just on what code is running but also on who is running it.
When an application uses JAAS authentication to authenticate the user (or other entity such as a service), a Subject is created as a result. The purpose of the Subject is to represent the authenticated user. A Subject is comprised of a set of Principals, where each Principal represents an identity for that user. For example, a Subject could have a name Principal ("Susan Smith") and a Social Security Number Principal ("987-65-4321"), thereby distinguishing this Subject from other Subjects.
Permissions can be granted in the policy to specific Principals. After the user has been authenticated, the application can associate the Subject with the current access control context. For each subsequent security-checked operation (a local file access, for example), the Java runtime will automatically determine whether the policy grants the required permission only to a specific Principal and if so, the operation will be allowed only if the Subject associated with the access control context contains the designated Principal.
How is JAAS Authorization Performed?
To make JAAS authorization take place, the following is required:
- The user must be authenticated, as described in the JAAS Authentication Tutorial tutorial.
- Principal-based entries must be configured in the security policy; see How Do You Make Principal-Based Policy File Statements?
- The Subject that is the result of authentication must be associated with the current access control context; see How Do You Associate a Subject with an Access Control Context?.
How Do You Make Principal-Based Policy File Statements?
Policy file grant
statements (see Default Policy Implementation and Policy File Syntax can now optionally include one or more Principal
fields. Inclusion of a Principal
field indicates that the user or other entity represented by the specified Principal
, executing the specified code, has the designated permissions.
Thus, the basic format of a grant
statement is now
grant <signer(s) field>, <codeBase URL>
<Principal field(s)> {
permission perm_class_name "target_name", "action";
....
permission perm_class_name "target_name", "action";
};
where each of the signer
, codeBase
and Principal
fields is optional and the order between the fields doesn't matter.
A Principal
field looks like the following:
Principal Principal_class "principal_name"
That is, it is the word Principal
(where case doesn't matter) followed by the (fully qualified) name of a Principal
class and a principal name.
A Principal
class is a class that implements the java.security.Principal interface. All Principal
objects have an associated name that can be obtained by calling their getName
method. The format used for the name is dependent on each Principal
implementation.
The type of Principal
placed in the Subject
created by the basic authentication mechanism used by this tutorial is SamplePrincipal
, so that is what should be used as the Principal_class
part of our grant
statement's Principal
designation. User names for SamplePrincipal
s are of the form name, and the only user name accepted for this tutorial is testUser
, so the principal_name
designation to use in the grant
statement is testUser
.
It is possible to include more than one Principal
field in a grant
statement. If multiple Principal
fields are specified, then the permissions in that grant
statement are granted only if the Subject
associated with the current access control context contains all of those Principal
s.
To grant the same set of permissions to different Principal
s, create multiple grant
statements where each lists the permissions and contains a single Principal
field designating one of the Principal
s.
The policy file for this tutorial includes one grant
statement with a Principal
field:
grant codebase "file:./SampleAction.jar",
Principal sample.principal.SamplePrincipal "testUser" {
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.io.FilePermission "foo.txt", "read";
};
This specifies that the indicated permissions are granted to the specified Principal
executing the code in SampleAction.jar
. (Note: the SamplePrincipal
class is in the sample.principal
package.)
How Do You Associate a Subject with an Access Control Context?
To create and associate a Subject with the current access control context, you need the following:
- The user must first be authenticated, as described in JAAS Authentication Tutorial.
- The static
doAs
method from the Subject class must be called, passing it an authenticated Subject and a java.security.PrivilegedAction or java.security.PrivilegedExceptionAction. (See Appendix A: API for Privileged Blocks in Permissions in the JDK for a comparison of PrivilegedAction and PrivilegedExceptionAction.) ThedoAs
method associates the provided Subject with the current access control context and then invokes therun
method from the action. Therun
method implementation contains all the code to be executed as the specified Subject. The action thus executes as the specified Subject.The static
doAsPrivileged
method from the Subject class may be called instead of thedoAs
method, as will be done for this tutorial. In addition to the parameters passed todoAs
,doAsPrivileged
requires a third parameter: an AccessControlContext. UnlikedoAs
, which associates the provided Subject with the current access control context,doAsPrivileged
associates the Subject with the provided access control context or with an empty access control context if the parameter passed in isnull
, as is the case for this tutorial. See doAs vs. doAsPrivileged in the JAAS Reference Guide for a comparison of those methods.
The Authorization Tutorial Code
The code for this tutorial consists of four files:
SampleAzn.java
is exactly the same as theSampleAcn.java
application file from the JAAS Authentication Tutorial tutorial except for the additional code needed to callSubject.doAsPrivileged
.SampleAction.java
contains theSampleAction
class. This class implements PrivilegedAction and has arun
method that contains all the code we want to be executed with Principal-based authorization checks.SampleLoginModule.java
is the class specified by the tutorial's login configuration file (see The Login Configuration File for the JAAS Authorization Tutorial) as the class implementing the desired underlying authentication.SampleLoginModule
's user authentication consists of simply verifying that the name and password specified by the user have specific values. This class was also used by the JAAS Authentication Tutorial tutorial and will not be discussed further here.SamplePrincipal.java
is a sample class implementing the java.security.Principal interface. It is used bySampleLoginModule
. This class was also used by the JAAS Authentication tutorial and will not be discussed further here.
The SampleLoginModule.java
and SamplePrincipal.java
files were also used in the JAAS Authentication Tutorial
tutorial, so they are not described further here. The following sections describe the
other source files
SampleAzn.java
Like SampleAcn
, the SampleAzn
class instantiates a LoginContext lc
and calls its login
method to perform the authentication. If successful, the authenticated Subject (which includes a SamplePrincipal representing the user) is obtained by calling the LoginContext's getSubject
method:
Subject mySubject = lc.getSubject();
After providing the user some information about the Subject, such as which Principals it has, the main
method then calls Subject.doAsPrivileged
, passing it the authenticated Subject mySubject
, a PrivilegedAction (SampleAction
) and a null
AccessControlContext, as described in the following.
The SampleAction
class is instantiated via the following:
PrivilegedAction action = new SampleAction();
The call to Subject.doAsPrivileged
is performed via:
Subject.doAsPrivileged(mySubject, action, null);
The doAsPrivileged
method invokes execution of the run
method in the PrivilegedAction action
(SampleAction
) to initiate execution of the rest of the code, which is considered to be executed on behalf of the Subject mySubject
.
Passing null
as the AccessControlContext (third) argument to doAsPrivileged
indicates that mySubject
should be associated with a new empty AccessControlContext. The result is that security checks occurring during execution of SampleAction
will only require permissions for the SampleAction
code itself (or other code it invokes), running as mySubject
. Note that the caller of doAsPrivileged
(and the callers on the execution stack at the time doAsPrivileged
was called) do not require any permissions while the action executes.
SampleAzn.java
package sample;
import java.io.*;
import java.util.*;
import java.security.Principal;
import java.security.PrivilegedAction;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import com.sun.security.auth.*;
/**
* This Sample application attempts to authenticate a user
* and executes a SampleAction as that user.
*
* If the user successfully authenticates itself,
* the username and number of Credentials is displayed.
*/
public class SampleAzn {
/**
* Attempt to authenticate the user.
*
* @param args input arguments for this application. These are ignored.
*/
public static void main(String[] args) {
// Obtain a LoginContext, needed for authentication. Tell it
// to use the LoginModule implementation specified by the
// entry named "Sample" in the JAAS login configuration
// file and to also use the specified CallbackHandler.
LoginContext lc = null;
try {
lc = new LoginContext("Sample", new MyCallbackHandler());
} catch (LoginException le) {
System.err.println("Cannot create LoginContext. "
+ le.getMessage());
System.exit(-1);
} catch (SecurityException se) {
System.err.println("Cannot create LoginContext. "
+ se.getMessage());
System.exit(-1);
}
// the user has 3 attempts to authenticate successfully
int i;
for (i = 0; i < 3; i++) {
try {
// attempt authentication
lc.login();
// if we return with no exception, authentication succeeded
break;
} catch (LoginException le) {
System.err.println("Authentication failed:");
System.err.println(" " + le.getMessage());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
// ignore
}
}
}
// did they fail three times?
if (i == 3) {
System.out.println("Sorry");
System.exit(-1);
}
System.out.println("Authentication succeeded!");
Subject mySubject = lc.getSubject();
// let's see what Principals we have
Iterator principalIterator = mySubject.getPrincipals().iterator();
System.out.println("Authenticated user has the following Principals:");
while (principalIterator.hasNext()) {
Principal p = (Principal)principalIterator.next();
System.out.println("\t" + p.toString());
}
System.out.println("User has " +
mySubject.getPublicCredentials().size() +
" Public Credential(s)");
// now try to execute the SampleAction as the authenticated Subject
PrivilegedAction action = new SampleAction();
Subject.doAsPrivileged(mySubject, action, null);
System.exit(0);
}
}
/**
* A CallbackHandler implemented by the application.
*
* This application is text-based. Therefore it displays information
* to the user using the OutputStreams System.out and System.err,
* and gathers input from the user using the InputStream System.in.
*/
class MyCallbackHandler implements CallbackHandler {
/**
* Invoke an array of Callbacks.
*
* @param callbacks an array of <code>Callback</code> objects which contain
* the information requested by an underlying security
* service to be retrieved or displayed.
*
* @exception java.io.IOException if an input or output error occurs. <p>
*
* @exception UnsupportedCallbackException if the implementation of this
* method does not support one or more of the Callbacks
* specified in the <code>callbacks</code> parameter.
*/
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {
// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback)callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.out.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.out.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " +
toc.getMessageType());
}
} else if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
} else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(System.console().readPassword());
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
SampleAction.java
SampleAction.java
contains the SampleAction
class. This class implements java.security.PrivilegedAction
and has a run
method that contains all the code we want to be executed as the Subject mySubject
. For this tutorial, we will perform three operations, each of which cannot be done unless code has been granted required permissions. We will:
- Read and print the value of the
java.home
system property, - Read and print the value of the
user.home
system property, and - Determine whether or not a file named
foo.txt
exists in the current directory.
Here is the code:
SampleAction.java
package sample;
import java.io.File;
import java.security.PrivilegedAction;
/**
* This is a Sample PrivilegedAction implementation, designed to be
* used with the Sample application.
*
*/
public class SampleAction implements PrivilegedAction {
/**
* This Sample PrivilegedAction performs the following operations:
* <ul>
* <li>Access the System property, <i>java.home</i></li>
* <li>Access the System property, <i>user.home</i></li>
* <li>Access the file, <i>foo.txt</i></li>
* </ul>
*
* @return <code>null</code> in all cases.
*
* @exception SecurityException if the caller does not have permission
* to perform the operations listed previously.
*/
public Object run() {
System.out.println("\nYour java.home property: "
+System.getProperty("java.home"));
System.out.println("\nYour user.home property: "
+System.getProperty("user.home"));
File f = new File("foo.txt");
System.out.print("\nfoo.txt does ");
if (!f.exists())
System.out.print("not ");
System.out.println("exist in the current working directory.");
return null;
}
}
The Login Configuration File for the JAAS Authorization Tutorial
The login configuration file used for this tutorial can be exactly the same as that used by the JAAS Authentication Tutorial tutorial. Thus we can use the sample_jaas.config
file, which contains just one entry:
Sample {
sample.module.SampleLoginModule required debug=true;
};
This entry is named Sample
and that is the name that both our tutorial applications SampleAcn
and SampleAzn
use to refer to it. The entry specifies that the LoginModule to be used to do the user authentication is the SampleLoginModule
in the sample.module
package and that this SampleLoginModule
is required to "succeed" in order for authentication to be considered successful. The SampleLoginModule
succeeds only if the name and password supplied by the user are the one it expects (testUser
and testPassword
, respectively).
The SampleLoginModule also defines a debug
option that can be set to true
as shown. If this option is set to true,
SampleLoginModule outputs extra information about the progress of authentication.
The Policy File
The application for this authorization tutorial consists of two classes, SampleAzn
and SampleAction
. The code in each class contains some security-sensitive operations and thus relevant permissions are required in a policy file in order for the operations to be executed.
The LoginModule used by this tutorial, SampleLoginModule
, also contains an operation requiring a permission.
The following sections describe the permissions required by each of these classes, followed by the full policy file.
Permissions Required by SampleAzn
The main method of the SampleAzn
class does two operations for which permissions are required. It
- creates a LoginContext, and
- calls the
doAsPrivileged
static method of the Subject class.
The LoginContext creation is exactly the same as was done in the authentication tutorial, and it thus needs the same javax.security.auth.AuthPermission
permission with target "createLoginContext.Sample
".
In order to call the doAsPrivileged
method of the Subject class, you need to have a javax.security.auth.AuthPermission
with target "doAsPrivileged
".
Assuming the SampleAzn
class is placed in a JAR file named SampleAzn.jar
, these permissions can be granted to the SampleAzn
code via the following grant
statement in the policy file:
grant codebase "file:./SampleAzn.jar" {
permission javax.security.auth.AuthPermission
"createLoginContext.Sample";
permission javax.security.auth.AuthPermission "doAsPrivileged";
};
Permissions Required by SampleAction
The SampleAction
code does three operations for which permissions are required. It
- reads the value of the
java.home
system property. - reads the value of the
user.home
system property. - checks to see whether or not a file named
foo.txt
exists in the current directory.
The permissions required for these operations are the following:
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.io.FilePermission "foo.txt", "read";
We need to grant these permissions to the code in SampleAction.class
, which we will place in a JAR file named SampleAction.jar
. However, for this particular grant
statement we want to grant the permissions not just to the code but to a specific user executing the code, to demonstrate how to restrict access to a particular user.
Thus, as explained in How Do You Make Principal-Based Policy File Statements?, our grant
statement looks like the following:
grant codebase "file:./SampleAction.jar", Principal sample.principal.SamplePrincipal "testUser" {
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.io.FilePermission "foo.txt", "read";
};
Permissions Required by SampleLoginModule
The SampleLoginModule
code does one operation for which permissions are required. It needs a javax.security.auth.AuthPermission
with target "modifyPrincipals
" in order to populate a Subject
with a Principal
. The grant statement is the following:
grant codebase "file:./SampleLM.jar" {
permission javax.security.auth.AuthPermission "modifyPrincipals";
};
The Full Policy File
The full policy file is sampleazn.policy
:
sampleazn.policy
/* grant the sample LoginModule permissions */
grant codebase "file:./SampleAction.jar", Principal sample.principal.SamplePrincipal "testUser" {
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.io.FilePermission "foo.txt", "read";
};
grant codebase "file:./SampleLM.jar" {
permission javax.security.auth.AuthPermission "modifyPrincipals";
};
grant codebase "file:./SampleAcn.jar" {
permission javax.security.auth.AuthPermission "createLoginContext.Sample";
};
Running the Authorization Tutorial Code
To execute our JAAS authorization tutorial code, all you have to do is
-
Place the following files into a directory:
sample_jaas.config
login configuration file (see The Login Configuration File for the JAAS Authorization Tutorial)sampleazn.policy
policy file
-
Create a subdirectory named
sample
of that top-level directory, and place the following into it (note theSampleAzn
andSampleAction
classes are in a package namedsample
):SampleAzn.java
source fileSampleAction.java
source file
-
Create a subdirectory of the
sample
directory and name itmodule
. Place the following into it (note theSampleLoginModule
class is in a package namedsample.module
):SampleLoginModule.java
source file
-
Create another subdirectory of the
sample
directory and name itprincipal
. Place the following into it (note theSamplePrincipal
class is in a package namedsample.principal
):SamplePrincipal.java
source file
-
While in the top-level directory, compile all the source files:
javac sample/SampleAction.java sample/SampleAzn.java sample/module/SampleLoginModule.java sample/principal/SamplePrincipal.java
(Type all that on one line.)
-
Create a JAR file named
SampleAzn.jar
containingSampleAzn.class
andMyCallbackHandler.class
(Note the sources for both these classes are inSampleAzn.java
):jar -cvf SampleAzn.jar sample/SampleAzn.class sample/MyCallbackHandler.class
(Type all that on one line.)
-
Create a JAR file named
SampleAction.jar
containingSampleAction.class
:jar -cvf SampleAction.jar sample/SampleAction.class
-
Create a JAR file containing
SampleLoginModule.class
andSamplePrincipal.class
:jar -cvf SampleLM.jar sample/module/SampleLoginModule.class sample/principal/SamplePrincipal.class
-
Execute the
SampleAzn
application, specifying- An appropriate
-classpath
clause that classes should be searched for in theSampleAzn.jar
,SampleAction.jar
, andSampleLM.jar
JAR files, -Djava.security.manager
that a security manager should be installedWARNING:
The Security Manager and APIs related to it have been deprecated and are subject to removal in a future release. There is no replacement for the Security Manager. See JEP 411 for discussion and alternatives.-Djava.security.policy==sampleazn.policy
that the policy file to be used issampleazn.policy
, and-Djava.security.auth.login.config==sample_jaas.config
that the login configuration file to be used issample_jaas.config
.
Note:
Use the double equals sign (
==
) with thejava.security.policy
property with care as it overrides the built-in JDK policy file, which grants a set of default permissions that are designed to provide a secure, out-of-the-box configuration for the JDK. Overriding this policy may result in unexpected behavior (JDK code may not be granted the right permissions) and should only be done by experienced users.If you use a single equals sign (
=
) with thejava.security.auth.login.config
system property (instead of a double equals sign (==
)), then the configurations specified by both this system property and thejava.security
file are used.The following are the full commands to use for Windows, Linux, and macOS. The only difference is that on Windows you use semicolons to separate class path items, while you use colons for that purpose on Linux and macOS.
Here is the full command for Windows:
java -classpath SampleAzn.jar;SampleAction.jar;SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleazn.policy -Djava.security.auth.login.config==sample_jaas.config sample.SampleAzn
Here is the full command for Linux and macOS:
java -classpath SampleAzn.jar:SampleAction.jar:SampleLM.jar -Djava.security.manager -Djava.security.policy==sampleazn.policy -Djava.security.auth.login.config==sample_jaas.config sample.SampleAzn
Type the full command on one line. Multiple lines are used here for legibility. If the command is too long for your system, you may need to place it in a
.bat
file (for Windows) or a.sh
file (for Linux and macOS) and then run that file to execute the command.You will be prompted for a user name and password (use
testUser
andtestPassword
), and theSampleLoginModule
specified in the login configuration file will check the name and password. If your login is successful, you will see the messageAuthentication succeeded!
and if not, you will seeAuthentication failed:
followed by a reason for the failure.Once authentication is successfully completed, the rest of the program (in
SampleAction
) will be executed on behalf of you, the user, requiring you to have been granted appropriate permissions. Thesampleazn.policy
policy file grants you the required permissions, so you will see a display of the values of yourjava.home
anduser.home
system properties and a statement as to whether or not you have a file namedfoo.txt
in the current directory. - An appropriate