Security Managers and Permissions


Once a class has been loaded into the virtual machine by a class loader or by the default class loading mechanism and checked by the verifier, the third security mechanism of the Java platform springs into action: the security manager. A security manager is a class that controls whether a specific operation is permitted. Operations checked by a security manager include the following:

  • Whether the current thread can create a new class loader

  • Whether the current thread can halt the virtual machine

  • Whether a class can access a member of another class

  • Whether the current thread can access a local file

  • Whether the current thread can open a socket connection to an external host

  • Whether a class can start a print job

  • Whether a class can access the system clipboard

  • Whether a class can access the AWT event queue

  • Whether the current thread is trusted to bring up a top-level window

There are many other checks such as these throughout the Java library.

The default behavior when running Java applications is that no security manager is installed, so all these operations are permitted. The applet viewer, on the other hand, immediately installs a security manager that is quite restrictive.

For example, applets are not allowed to exit the virtual machine. If they try calling the exit method, then a security exception is thrown. Here is what happens in detail. The exit method of the Runtime class calls the checkExit method of the security manager. Here is the entire code of the exit method:

 public void exit(int status) {    SecurityManager security = System.getSecurityManager();    if (security != null)        security.checkExit(status);    exitInternal(status); } 

The security manager now checks if the exit request came from the browser or an individual applet. If the security manager agrees with the exit request, then the checkExit method simply returns and normal processing continues. However, if the security manager doesn't want to grant the request, the checkExit method throws a SecurityException.

The exit method continues only if no exception occurred. It then calls the private native exitInternal method that actually terminates the virtual machine. There is no other way of terminating the virtual machine, and because the exitInternal method is private, it cannot be called from any other class. Thus, any code that attempts to exit the virtual machine must go through the exit method and thus through the checkExit security check without triggering a security exception.

Clearly, the integrity of the security policy depends on careful coding. The providers of system services in the standard library must be careful to always consult the security manager before attempting any sensitive operation.

When you run a Java application, the default is that no security manager is running. Your program can install a specific security manager by a call to the static setSecurityManager method in the System class. Once your program installs a security manager, any attempt to install a second security manager succeeds only if the first security manager agrees to be replaced. This is clearly essential; otherwise, a bad applet could install its own security manager. Thus, although it is possible to have multiple class loaders, a program in the Java programming language can be governed by only one security manager. It is up to the implementor of that security manager to decide whether to grant all classes the same access or whether to take the origins of the classes into account before deciding what to do.

The default security manager of the Java 2 platform allows both programmers and system administrators fine-grained control over individual security permissions. We describe these features in the following section. First, we summarize the Java 2 platform security model. We then show how you can control permissions with policy files. Finally, we explain how you can define your own permission types and how you can extend the default security manager class.

Java 2 Platform Security

JDK 1.0 had a very simple security model: Local classes had full permissions, and remote classes were confined to the sandbox. Just like a child that can only play in a sandbox, remote code was only allowed to paint on the screen and interact with the user. The applet security manager denied all access to local resources. JDK 1.1 implemented a slight modification: Remote code that was signed by a trusted entity was granted the same permissions as local classes. However, both versions of the JDK provided an all-or-nothing approach. Programs either had full access or they had to play in the sandbox.

The Java 2 platform has a much more flexible mechanism. A security policy maps code sources to permission sets (see Figure 9-4).

Figure 9-4. A security policy


A code source has two properties: the code location (for example, an HTTP URL for remote code, or a file URL for local code in a JAR file) and certificates. You see later in this chapter how code can be certified by trusted parties.

A permission is any property that is checked by a security manager. JDK 1.2 implementation supports a number of permission classes, each of which encapsulates the details of a particular permission. For example, the following instance of the FilePermission class states that it is okay to read and write any file in the /tmp directory.

 FilePermission p = new FilePermission("/tmp/*", "read,write"); 

More important, the default implementation of the Policy class in JDK 1.2 reads permissions from a permission file. Inside a permission file, the same read permission is expressed as

 permission java.io.FilePermission "/tmp/*", "read,write"; 

We discuss permission files in the next section.

Figure 9-5 shows the hierarchy of the permission classes that were supplied with JDK 1.2. Many more permission classes have been added in subsequent JDK versions.

Figure 9-5. The hierarchy of permission classes


In the preceding section, you saw that the SecurityManager class has security check methods such as checkExit. These methods exist only for the convenience of the programmer and for backward compatibility. They all map into standard permission checks. For example, here is the source code for the checkExit method:

 public void checkExit() {    checkPermission(new RuntimePermission("exitVM")); } 

Each security manager is free to provide its own implementation of the checkPermission method. However, the JDK provides a "standard model" of how to carry out permission checks. For the remainder of this section, we describe this standard model.

Each class has a protection domain, an object that encapsulates both the code source and the collection of permissions of the class. When the SecurityManager needs to check a permission, it looks at the classes of all methods currently on the call stack. It then gets the protection domains of all classes and asks each protection domain if its permission collection allows the operation that is currently being checked. If all domains agree, then the check passes. Otherwise, a SecurityException is thrown.

Why do all methods on the call stack need to allow a particular operation? Let us work through an example. Suppose the init method of an applet wants to open a file. It might call

 Reader in = new FileReader(name); 

The FileReader constructor calls the FileInputStream constructor, which calls the checkRead method of the security manager, which finally calls checkPermission with a FilePermission(name, "read" object. Table 9-1 shows the call stack.

Table 9-1. Call Stack During Permission Checking

Class

Method

Code Source

Permissions

SecurityManager

checkPermission

null

AllPermission

SecurityManager

checkRead

null

AllPermission

FileInputStream

constructor

null

AllPermission

FileReader

constructor

null

AllPermission

applet

init

applet code source

applet permissions

. . .

   


The FileInputStream and SecurityManager classes are system classes whose CodeSource is null and whose permissions consist of an instance of the AllPermission class, which allows all operations. Clearly, their permissions alone can't determine the outcome of the check. As you can see, the checkPermission method must take into account the restricted permissions of the applet class. By checking the entire call stack, the security mechanism ensures that one class can never ask another class to carry out a sensitive operation on its behalf.

NOTE

This brief discussion of permission checking explains the basic concepts. However, we omit a number of technical details here. With security, the devil lies in the details, and we encourage you to read the book by Li Gong for more information. For a more critical view of the Java platform security model, see the book Securing Java by Gary McGraw and Ed Felten [John Wiley & Sons 1999]. You can find an online version of that book at http://www.securingjava.com.



 java.lang.SecurityManager 1.0 

  • void checkPermission(Permission p) 1.2

    check whether this security manager grants the given permission. The method throws a SecurityException if the permission is not granted.


 java.lang.Class 1.0 

  • ProtectionDomain getProtectionDomain() 1.2

    gets the protection domain for this class, or null if this class was loaded without a protection domain.


 java.security.ProtectionDomain 1.2 

  • ProtectionDomain(CodeSource source, PermissionCollection permissions)

    constructs a protection domain with the given code source and permissions.

  • CodeSource getCodeSource()

    gets the code source of this protection domain.

  • boolean implies(Permission p)

    returns true if the given permission is allowed by this protection domain.


 java.security.CodeSource 1.2 

  • Certificate[] getCertificates()

    gets the certificates for class file signature associated with this code source.

  • URL getLocation()

    gets the location of class files associated with this code source.

Security Policy Files

In the preceding section, you saw how the SecureClassLoader assigns permissions when loading classes, that is, by asking a Policy object to look up the permissions for the code source of each class. In principle, you can install your own Policy class to carry out the mapping from code sources to permissions. However, in this section, you learn about the standard policy class that the Java 2 virtual machine uses.

NOTE

The policy class is set in the file java.security in the jre/lib/security subdirectory of the JDK home directory. By default, this file contains the line

 policy.provider=sun.security.provider.PolicyFile 

You can supply your own policy class and install it by changing this file.


The standard policy reads policy files that contain instructions for mapping code sources to permissions. You have seen these policy files in Chapter 5, where you saw that they were required to grant network access to programs that use the RMISecurityManager. Here is a typical policy file:

 grant codeBase "http://www.horstmann.com/classes" {    permission java.io.FilePermission "/tmp/*", "read,write"; }; 

This file grants permission to read and write files in the /tmp directory to all code that was downloaded from http://www.horstmann.com/classes.

You can install policy files in standard locations. By default, there are two locations:

  • The file java.policy in the Java platform home directory

  • The file .java.policy (notice the period at the beginning of the file name) in the user home directory

NOTE

You can change the locations of these files in the java.security configuration file. The defaults are specified as

 policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy 

A system administrator can modify the java.security file and specify policy URLs that reside on another server and that cannot be edited by users. There can be any number of policy URLs (with consecutive numbers) in the policy file. The permissions of all files are combined.


During testing, we don't like to constantly modify these standard files. Therefore, we prefer to explicitly name the policy file that is required for each application. Simply place the permissions into a separate file, say, MyApp.policy, and start the virtual machine as

 java -Djava.security.policy=MyApp.policy MyApp 

For applets, you instead use

 appletviewer -J-Djava.security.policy=MyApplet.policy MyApplet.html 

(You can use the -J option of the appletviewer to pass any command-line argument to the virtual machine.)

In these examples, the MyApp.policy file is added to the other policies in effect. If you add a second equal sign, such as

 java -Djava.security.policy==MyApp.policy MyApp 

then your application uses only the specified policy file, and the standard policy files are ignored.

CAUTION

An easy mistake during testing is to accidentally leave a .java.policy file that grants a lot of permissions, perhaps even AllPermission, in the current directory. If you find that your application doesn't seem to pay attention to the restrictions in your policy file, check for a left-behind .java.policy file in your current directory. If you use a UNIX system, this is a particularly easy mistake to make because files with names that start with a period are not displayed by default.


As you saw previously, Java applications by default do not install a security manager. Therefore, you won't see the effect of policy files until you install one. You can, of course, add a line

 System.setSecurityManager(new SecurityManager()); 

into your main method. Or you can add the command-line option -Djava.security.manager when starting the virtual machine.

 java -Djava.security.manager -Djava.security.policy=MyApp.policy MyApp 

In the remainder of this section, we show you in detail how to describe permissions in the policy file. We describe the entire policy file format, except for code certificates, which we cover later in this chapter.

A policy file contains a sequence of grant entries. Each entry has the following form:


grant codesource
{
   permission1;
   permission2;
   . . .
};

The code source contains a code base (which can be omitted if the entry applies to code from all sources) and the names of trusted principals and certificate signers (which can be omitted if signatures are not required for this entry).

The code base is specified as

 codeBase "url" 

If the URL ends in a /, then it refers to a directory. Otherwise, it is taken to be the name of a JAR file. For example,

 grant codeBase "www.horstmann.com/classes/" { . . . }; grant codeBase "www.horstmann.com/classes/MyApp.jar" { . . . }; 

The code base is a URL and should always contain forward slashes as file separators, even for file URLs in Windows. For example,

 grant codeBase "file:C:/myapps/classes/" { . . . }; 

NOTE

Everyone knows that http URLs start with two slashes (http://). But there seems sufficient confusion about file URLs that the policy file reader accepts two forms of file URLs, namely, file://localFile and file:localFile. Furthermore, a slash before a Windows drive letter is optional. That is, all of the following are acceptable:

 file:C:/dir/filename.ext file:/C:/dir/filename.ext file://C:/dir/filename.ext file:///C:/dir/filename.ext 

Actually, in our tests, the file:////C:/dir/filename.ext is acceptable as well, and we have no explanation for that. In UNIX/Linux, you should use the form

 file:/dir/filename.ext 


The permissions have the following structure:


permission className targetName, actionList;

The class name is the fully qualified class name of the permission class (such as java.io.FilePermission). The target name is a permission-specific value, for example, a file or directory name for the file permission, or a host and port for a socket permission. The actionList is also permission specific. It is a list of actions, such as read or connect, separated by commas. Some permission classes don't need target names and action lists. Table 9-2 lists the commonly used permission classes and their actions.

Table 9-2. Permissions and Their Associated Targets and Actions

Permission

Target

Action

java.io.FilePermission

file target (see text)

read, write, execute, delete

java.net.SocketPermission

socket target (see text)

accept, connect, listen, resolve

java.util.PropertyPermission

property target (see text)

read, write

java.lang.RuntimePermission

createClassLoader
getClassLoader
setContextClassLoader
enableContextClassLoaderOverride
createSecurityManager
setSecurityManager
exitVM
getenv.variableName
shutdownHooks
setFactory
setIO
modifyThread
stopThread
modifyThreadGroup
getProtectionDomain
readFileDescriptor
writeFileDescriptor
loadLibrary.libraryName
accessClassInPackage.packageName
defineClassInPackage.packageName
accessDeclaredMembers.className
queuePrintJob
getStackTrace
setDefaultUncaughtExceptionHandler
preferences

 

java.awt.AWTPermission

 showWindowWithoutWarningBanner accessClipboard accessEventQueue createRobot fullScreenExclusive listenToAllAWTEvents readDisplayPixels replaceKeyboardFocusManager watchMousePointer setWindowAlwaysOnTop setAppletStub 

 

java.net.NetPermission

 setDefaultAuthenticator specifyStreamHandler requestPasswordAuthentication setProxySelector getProxySelector setCookieHandler getCookieHandler setResponseCache getResponseCache 

 

java.lang.reflect.ReflectPermission

suppressAccessChecks

 

java.io.SerializablePermission

 enableSubclassImplementation enableSubstitution 

 

java.security.SecurityPermission

createAccessControlContext
getdomainCombiner
getPolicy
setPolicy
getProperty.keyName
setProperty.keyName
insertProvider.providerName
removeProvider.providerName
setSystemScope
setIdentityPublicKey
setIdentityInfo
addIdentityCertificate
removeIdentityCertificate
printIdentity
clearProviderProperties.providerName
putProviderProperty.providerName
removeProviderProperty.providerName
getSignerPrivateKey
setSignerKeyPair

 

java.security.AllPermission

  

javax.audio.AudioPermission

 play record 

 

javax.security.auth.AuthPermission

doAs
doAsPrivileged
getSubject
getSubjectFromDomainCombiner
setReadOnly
modifyPrincipals
modifyPublicCredentials
modifyPrivateCredentials
refreshCredential
destroyCredential
createLoginContext.contextName
getLoginConfiguration
setLoginConfiguration
refreshLoginConfiguration

 

java.util.logging.LoggingPermission

control

 

java.sql.SQLPermission

setLog

 


As you can see from Table 9-2, most permissions simply permit a particular operation. You can think of the operation as the target with an implied action "permit". These permission classes all extend the BasicPermission class (see Figure 9-5 on page 708). However, the targets for the file, socket, and property permissions are more complex, and we need to investigate them in detail.

File permission targets can have the following form:

file

a file

directory/

a directory

directory/*

all files in the directory

*

all files in the current directory

directory/-

all files in the directory or one of its subdirectories

-

all files in the current directory or one of its subdirectories

<<ALL FILES>>

all files in the file system


For example, the following permission entry gives access to all files in the directory /myapp and any of its subdirectories.

 permission java.io.FilePermission "/myapp/-", "read,write,delete"; 

You must use the \\ escape sequence to denote a backslash in a Windows file name.

 permission java.io.FilePermission "c:\\myapp\\-",  "read,write,delete"; 

Socket permission targets consist of a host and a port range. Host specifications have the following form:

hostname or IPaddress

a single host

localhost or the empty string

the local host

*.domainSuffix

any host whose domain ends with the given suffix

*

all hosts


Port ranges are optional and have the form:

:n

a single port

:n-

all ports numbered n and above

:-n

all ports numbered n and below

:n1-n2

all ports in the given range


Here is an example:

 permission java.net.SocketPermission "*.horstmann.com:8000-8999", "connect"; 

Finally, property permission targets can have one of two forms:

property

a specific property

propertyPrefix.*

all properties with the given prefix


Examples are "java.home" and "java.vm.*"

For example, the following permission entry allows a program to read all properties that start with java.vm.

 permission java.util.PropertyPermission "java.vm.*", "read"; 

You can use system properties in policy files. The token ${property} is replaced by the property value. For example, ${user.home} is replaced by the home directory of the user. Here is a typical use of this system property in a permission entry.

 permission java.io.FilePermission "${user.home}", "read,write"; 

To create platform-independent policy files, it is a good idea to use the file.separator property instead of explicit / or \\ separators. To make this simpler, the special notation ${/} is a shortcut for ${file.separator}. For example,

 permission java.io.FilePermission "${user.home}${/}-", "read,write"; 

is a portable entry for granting permission to read and write in the user's home directory and any of its subdirectories.

NOTE

The JDK comes with a rudimentary tool, called policytool, that you can use to edit policy files (see Figure 9-6). Of course, this tool is not suitable for end users who would be completely mystified by most of the settings. We view it as a proof of concept for an administration tool that might be used by system administrators who prefer point-and-click over syntax. Still, what's missing is a sensible set of categories (such as low, medium, or high security) that is meaningful to nonexperts. As a general observation, we believe that the Java 2 platform certainly contains all the pieces for a fine-grained security model but that it could benefit from some polish in delivering these pieces to end users and system administrators.

Figure 9-6. The policy tool



Custom Permissions

In this section, you see how you can supply your own permission class that users can refer to in their policy files.

To implement your permission class, you extend the Permission class and supply the following methods:

  • A constructor with two String parameters, for the target and the action list

  • String getActions()

  • boolean equals()

  • int hashCode()

  • boolean implies(Permission other)

The last method is the most important. Permissions have an ordering, in which more general permissions imply more specific ones. Consider the file permission

 p1 = new FilePermission("/tmp/-", "read, write"); 

This permission allows reading and writing of any file in the /tmp directory and any of its subdirectories.

This permission implies other, more specific permissions:

 p2 = new FilePermission("/tmp/-", "read"); p3 = new FilePermission("/tmp/aFile", "read, write"); p4 = new FilePermission("/tmp/aDirectory/-", "write"); 

In other words, a file permission p1 implies another file permission p2 if

  1. The target file set of p1 contains the target file set of p2;

  2. The action set of p1 contains the action set of p2.

Consider the following example of the use of the implies method. When the FileInputStream constructor wants to open a file for reading, it checks whether it has permission to do so. For that check, a specific file permission object is passed to the checkPermission method:

 checkPermission(new FilePermission(fileName, "read")); 

The security manager now asks all applicable permissions whether they imply this permission. If any one of them implies it, then the check passes.

In particular, the AllPermission implies all other permissions.

If you define your own permission classes, then you need to define a suitable notion of implication for your permission objects. Suppose, for example, that you define a TVPermission for a set-top box powered by Java technology. A permission

 new TVPermission("Tommy:2-12:1900-2200", "watch,record") 

might allow Tommy to watch and record television channels 212 between 19:00 and 22:00. You need to implement the implies method so that this permission implies a more specific one, such as

 new TVPermission("Tommy:4:2000-2100", "watch") 

Implementation of a Permission Class

In the next sample program, we implement a new permission for monitoring the insertion of text into a text area. The program ensures that you cannot add "bad words" such as sex, drugs, and C++ into a text area. We use a custom permission class so that the list of bad words can be supplied in a policy file.

The following subclass of JTextArea asks the security manager whether it is okay to add new text.

 class WordCheckTextArea extends JTextArea {    public void append(String text)    {       WordCheckPermission p = new WordCheckPermission(text, "insert");       SecurityManager manager = System.getSecurityManager();       if (manager != null) manager.checkPermission(p);       super.append(text);    } } 

If the security manager grants the WordCheckPermission, then the text is appended. Otherwise, the checkPermission method throws an exception.

Word check permissions have two possible actions: insert (the permission to insert a specific text) and avoid (the permission to add any text that avoids certain bad words). You should run this program with the following policy file:

 grant {    permission WordCheckPermission "sex,drugs,C++", "avoid"; }; 

This policy file grants the permission to insert any text that avoids the bad words sex, drugs, and C++.

When designing the WordCheckPermission class, we must pay particular attention to the implies method. Here are the rules that control whether permission p1 implies permission p2.

  1. If p1 has action avoid and p2 has action insert, then the target of p2 must avoid all words in p1. For example, the permission

     WordCheckPermission "sex,drugs,C++", "avoid" 

    implies the permission

     WordCheckPermission "Mary had a little lamb", "insert" 

  2. If p1 and p2 both have action avoid, then the word set of p2 must contain all words in the word set of p1. For example, the permission

     WordCheckPermission "sex,drugs", "avoid" 

    implies the permission

     WordCheckPermission "sex,drugs,C++", "avoid" 

  3. If p1 and p2 both have action insert, then the text of p1 must contain the text of p2. For example, the permission

     WordCheckPermission "Mary had a little lamb", "insert" 

    implies the permission

     WordCheckPermission "a little lamb", "insert" 

You can find the implementation of this class in Example 9-5.

Note that you retrieve the permission target with the confusingly named getName method of the Permission class.

Since permissions are described by a pair of strings in policy files, permission classes need to be prepared to parse these strings. In particular, we use the following method to transform the comma-separated list of bad words of an avoid permission into a genuine Set.

 public Set<String> badWordSet() {    Set<String> set = new HashSet<String>();    set.addAll(Arrays.asList(getName().split(",")));    return set; } 

This code allows us to use the equals and containsAll methods to compare sets. As you saw in Chapter 2, the equals method of a set class finds two sets to be equal if they contain the same elements in any order. For example, the sets resulting from "sex,drugs,C++" and "C++,drugs,sex" are equal.

CAUTION

Make sure that your permission class is a public class. The policy file loader cannot load classes with package visibility outside the boot class path, and it silently ignores any classes that it cannot find.


The program in Example 9-4 shows how the WordCheckPermission class works. Type any text into the text field and click the Insert button. If the security check passes, the text is appended to the text area. If not, an error message is displayed (see Figure 9-7).

Figure 9-7. The PermissionTest program


Make sure to start the program with the appropriate policy file.

 java -Djava.security.policy=PermissionTest.policy PermissionTest 

Otherwise, all attempts to insert text will fail.

CAUTION

If you carefully look at Figure 9-7, you will see that the frame window has a warning border with the misleading caption "Java Applet Window." The window caption is determined by the showWindowWithoutWarningBanner target of the java.awt.AWTPermission. If you like, you can edit the policy file to grant that permission.


Example 9-4. PermissionTest.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import java.io.*;  4. import java.net.*;  5. import java.security.*;  6. import java.util.*;  7. import javax.swing.*;  8.  9. /** 10.    This class demonstrates the custom WordCheckPermission. 11. */ 12. public class PermissionTest 13. { 14.    public static void main(String[] args) 15.    { 16.       System.setSecurityManager(new SecurityManager()); 17.       JFrame frame = new PermissionTestFrame(); 18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19.       frame.setVisible(true); 20.    } 21. } 22. 23. /** 24.    This frame contains a text field for inserting words into 25.    a text area that is protected from "bad words". 26. */ 27. class PermissionTestFrame extends JFrame 28. { 29.    public PermissionTestFrame() 30.    { 31.       setTitle("PermissionTest"); 32.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 33. 34.       textField = new JTextField(20); 35.       JPanel panel = new JPanel(); 36.       panel.add(textField); 37.       JButton openButton = new JButton("Insert"); 38.       panel.add(openButton); 39.       openButton.addActionListener(new 40.          ActionListener() 41.          { 42.             public void actionPerformed(ActionEvent event) 43.             { 44.                insertWords(textField.getText()); 45.             } 46.          }); 47. 48.       add(panel, BorderLayout.NORTH); 49. 50.       textArea = new WordCheckTextArea(); 51.       add(new JScrollPane(textArea), BorderLayout.CENTER); 52.    } 53. 54.    /** 55.       Tries to insert words into the text area. 56.       Displays a dialog if the attempt fails. 57.       @param words the words to insert 58.    */ 59.    public void insertWords(String words) 60.    { 61.       try 62.       { 63.          textArea.append(words + "\n"); 64.       } 65.       catch (SecurityException e) 66.       { 67.          JOptionPane.showMessageDialog(this, "I am sorry, but I cannot do that."); 68.       } 69.    } 70. 71.    private JTextField textField; 72.    private WordCheckTextArea textArea; 73.    private static final int DEFAULT_WIDTH = 400; 74.    private static final int DEFAULT_HEIGHT = 300; 75. } 76. 77. /** 78.    A text area whose append method makes a security check 79.    to see that no bad words are added. 80. */ 81. class WordCheckTextArea extends JTextArea 82. { 83.    public void append(String text) 84.    { 85.       WordCheckPermission p = new WordCheckPermission(text, "insert"); 86.       SecurityManager manager = System.getSecurityManager(); 87.       if (manager != null) manager.checkPermission(p); 88.       super.append(text); 89.    } 90. } 

Example 9-5. WordCheckPermission.java
  1. import java.security.*;  2. import java.util.*;  3.  4. /**  5.    A permission that checks for bad words.  6. */  7. public class WordCheckPermission extends Permission  8. {  9.    /** 10.       Constructs a word check permission 11.       @param target a comma separated word list 12.       @param anAction "insert" or "avoid" 13.    */ 14.    public WordCheckPermission(String target, String anAction) 15.    { 16.       super(target); 17.       action = anAction; 18.    } 19. 20.    public String getActions() { return action; } 21. 22.    public boolean equals(Object other) 23.    { 24.       if (other == null) return false; 25.       if (!getClass().equals(other.getClass())) return false; 26.       WordCheckPermission b = (WordCheckPermission) other; 27.       if (!action.equals(b.action)) return false; 28.       if (action.equals("insert")) 29.          return getName().equals(b.getName()); 30.       else if (action.equals("avoid")) 31.          return badWordSet().equals(b.badWordSet()); 32.       else return false; 33.    } 34. 35.    public int hashCode() 36.    { 37.       return getName().hashCode() + action.hashCode(); 38.    } 39. 40.    public boolean implies(Permission other) 41.    { 42.       if (!(other instanceof WordCheckPermission)) return false; 43.       WordCheckPermission b = (WordCheckPermission) other; 44.       if (action.equals("insert")) 45.       { 46.          return b.action.equals("insert") && 47.             getName().indexOf(b.getName()) >= 0; 48.       } 49.       else if (action.equals("avoid")) 50.       { 51.          if (b.action.equals("avoid")) 52.             return b.badWordSet().containsAll(badWordSet()); 53.          else if (b.action.equals("insert")) 54.          { 55.             for (String badWord : badWordSet()) 56.                if (b.getName().indexOf(badWord) >= 0) 57.                   return false; 58.             return true; 59.          } 60.          else return false; 61.       } 62.       else return false; 63.    } 64. 65.    /** 66.       Gets the bad words that this permission rule describes. 67.       @return a set of the bad words 68.    */ 69.    public Set<String> badWordSet() 70.    { 71.       Set<String> set = new HashSet<String>(); 72.       set.addAll(Arrays.asList(getName().split(","))); 73.       return set; 74.    } 75. 76.    private String action; 77. } 


 java.security.Permission 1.2 

  • Permission(String name)

    constructs a permission with the given target name.

  • String getName()

    returns the target name of this permission.

  • boolean implies(Permission other)

    checks whether this permission implies the other permission. That is the case if the other permission describes a more specific condition that is a consequence of the condition described by this permission.

A Custom Security Manager

In this section, we show you how to build a simple security manager. In your own programs, you are better off using custom permissions rather than implementing a full security manager, but we thought you might find it interesting to see how a security manager works internally. Our security manager monitors all file access and ensures that you can't open a text file if it contains forbidden words such as sex, drugs, and C++.

We monitor file access by overriding the checkPermission method of the standard security manager class. If the permission isn't a file read permission, then we simply call super.checkPermission. To check that it is permissible to read from a file, we open the file and scan its contents. We grant access to the file only when it doesn't contain any of the forbidden words. (We only monitor files with extension .txt since we don't want to block access to system and property files.)

 public class WordCheckSecurityManager extends SecurityManager {    public void checkPermission(Permission p)    {       if (p instanceof FilePermission && p.getActions().equals("read"))       {          String fileName = p.getName();          if (containsBadWords(fileName))             throw new SecurityException("Bad words in " + fileName);       }       else super.checkPermission(p);    }    . . . } 

NOTE

Another way of being notified of file read requests is to override the checkRead method. The SecurityManager class implements this method to call the checkPermission method with a FilePermission object. Close to 30 similar methods in the SecurityManager exist for historical reasons. The permission system has been introduced in the Java 2 platform. We recommend that you do not override these methods but instead carry out all permission checks in the checkPermission method.


There is just one catch in our file check scenario. Consider one possible flow of events.

  • A method of some class opens a file.

  • Then, the security manager springs into action and uses its checkPermission method.

  • The checkPermission method calls the containsBadWords method.

But the containsBadWords method must itself read the file to check its contents, which calls the security manager again! This would result in an infinite regression unless the security manager has a way of finding out in which context it was called. The getClassContext method is the way to find out how the method was called. This method returns an array of class objects that gives all the classes whose calls are currently pending. For example, when the security manager is called for the first time, that array is

 class WordCheckSecurityManager class SecurityManager class java.io.FileInputStream class java.io.FileReader class SecurityManagerFrame . . . class java.awt.EventDispatchThread 

The class at index 0 gives the currently executing call. Unfortunately, you only get to see the classes, not the names of the pending methods. When the security manager itself attempts to open the file, it is called again and the getClassContext method returns the following array:

 class WordCheckSecurityManager class SecurityManager class java.io.FileInputStream class java.io.FileReader class WordCheckSecurityManager class WordCheckSecurityManager class SecurityManager class java.io.FileInputStream class java.io.FileReader class SecurityManagerFrame . . . class java.awt.EventDispatchThread 

In this case, the security manager should permit the file access. How can we do this? We could test whether

 getClassContext()[0] == getClassContext()[4] 

but this approach is fragile. Here's an obvious case of where it can go wrong: Imagine that if the implementation changed, for example, so the FileReader constructor calls the security manager directly, then the test would be meaningless because the positions would not be the same in the array. It is far more robust to test whether any of the pending calls came from the same security manager.

Here is a method that carries out this test. Since this method is called from checkPermission, there are at least two copies of the security manager class on the call stack. We skip these first and then look for another instance of the same security manager.

 boolean inSameManager() {    Class[] cc = getClassContext();    // skip past current set of calls to this manager    int i = 0;    while (i < cc.length && cc[0] == cc[i])       i++;    // check if there is another call to this manager    while (i < cc.length)    {       if (cc[0] == cc[i]) return true;       i++;    }    return false; } 

We call this method in the checkPermission method. If we find that the security manager is invoked recursively, then we do not call the containsBadWords method again.

 if (p instanceof FilePermission && p.getActions().equals("read")) {    if (inSameManager())       return;    String fileName = p.getName();    if (containsBadWords(fileName))       throw new SecurityException("Bad words in " + fileName); } 

Example 9-6 shows a program that puts this security manager to work. The security manager is installed in the main method. When running the program, you can specify a file. The program will load its contents into the text area. However, if the file fails the security check, the program catches the security exception and displays a message instead (see Figure 9-8). For example, you can display "Alice in Wonderland," but the program refuses to load "The Count of Monte Cristo."

Figure 9-8. The SecurityManagerTest program


NOTE

You may wonder why we use a text field instead of a JFileChooser to select the file name. The JFileChooser calls the isHidden method of the File class for each file in the current directory, and that method tries to get read permission from the security manager.


Be careful how you invoke this program. The WordCheckSecurityManager class itself needs to be given AllPermission. The reason for this is subtle. The WordCheckSecurityManager class calls the SecurityManager superclass for all permissions other than file read permissions. When the SecurityManager class evaluates a permission, it checks whether all methods on the call stack should be granted that particular permission. The WordCheckSecurityManager is one of those classes. But it is not a system class, so you must explicitly grant it all permissions without also granting all permissions to the other classes of the program.

To separate the WordCheckSecurityManager class files from the other class files, make a JAR file containing just that class file.

 jar cvf WordCheck.jar WordCheckSecurityManager.class 

Then delete the WordCheckSecurityManager.class file.

Next, create a policy file, WordCheck.policy, with the following contents:

 grant codeBase "file:WordCheck.jar" {    permission java.security.AllPermission; }; 

This policy grants all permissions to the classes in the WordCheck.jar file. Finally, start the application as follows:

 java -classpath .:WordCheck.jar -Djava.security.policy=WordCheck.policy SecurityManagerTest 

On Windows, use a semicolon to separate the class path elements:

 java -classpath .;WordCheck.jar . . . 

TIP

If you are thinking of changing the security manager in your own programs, you should first investigate whether you can instead use the standard security manager and a custom permission, as described in the preceding section. Writing a security manager is error prone and can cause subtle security flaws. It is much better to use the standard security manager and augment the permission system instead.


Example 9-6. SecurityManagerTest.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import java.io.*;  4. import java.net.*;  5. import java.util.*;  6. import javax.swing.*;  7.  8. /**  9.    This class demonstrates the use of a custom security manager 10.    that prohibits the reading of text files containing bad words. 11. */ 12. public class SecurityManagerTest 13. { 14.    public static void main(String[] args) 15.    { 16.       System.setSecurityManager(new WordCheckSecurityManager()); 17.       JFrame frame = new SecurityManagerFrame(); 18.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19.       frame.setVisible(true); 20.    } 21. } 22. 23. /** 24.    This frame contains a text field to enter a file name and 25.    a text area to show the contents of the loaded file. 26. */ 27. class SecurityManagerFrame extends JFrame 28. { 29.    public SecurityManagerFrame() 30.    { 31.       setTitle("SecurityManagerTest"); 32.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 33. 34.       fileNameField = new JTextField(20); 35.       JPanel panel = new JPanel(); 36.       panel.add(new JLabel("Text file:")); 37.       panel.add(fileNameField); 38.       JButton openButton = new JButton("Open"); 39.       panel.add(openButton); 40.       openButton.addActionListener(new 41.          ActionListener() 42.          { 43.             public void actionPerformed(ActionEvent event) 44.             { 45.                loadFile(fileNameField.getText()); 46.             } 47.          }); 48. 49.       add(panel, "North"); 50. 51.       fileText = new JTextArea(); 52.       add(new JScrollPane(fileText), "Center"); 53.    } 54. 55.    /** 56.       Attempt to load a file into the text area. If a security exception is caught, 57.       a message is inserted into the text area instead. 58.       @param filename the file name 59.    */ 60.    public void loadFile(String filename) 61.    { 62.       try 63.       { 64.          fileText.setText(""); 65.          Scanner in = new Scanner(new FileReader(filename)); 66.          while (in.hasNextLine()) fileText.append(in.nextLine() + "\n"); 67.          in.close(); 68.       } 69.       catch (IOException e) 70.       { 71.          fileText.append(e + "\n"); 72.       } 73.       catch (SecurityException e) 74.       { 75.          fileText.append("I am sorry, but I cannot do that.\n"); 76.          fileText.append(e + "\n"); 77.       } 78.    } 79. 80.    private JTextField fileNameField; 81.    private JTextArea fileText; 82.    private static final int DEFAULT_WIDTH = 400; 83.    private static final int DEFAULT_HEIGHT = 300; 84. } 

Example 9-7. WordCheckSecurityManager.java
  1. import java.io.*;  2. import java.security.*;  3. import java.util.*;  4.  5. /**  6.    This security manager checks whether bad words are  7.    encountered when reading a file.  8. */  9. public class WordCheckSecurityManager extends SecurityManager 10. { 11.    public void checkPermission(Permission p) 12.    { 13.       if (p instanceof FilePermission && p.getActions().equals("read")) 14.       { 15.          if (inSameManager()) 16.             return; 17.          String fileName = p.getName(); 18.          if (containsBadWords(fileName)) 19.             throw new SecurityException("Bad words in " + fileName); 20.       } 21.       else super.checkPermission(p); 22.    } 23. 24.    /** 25.       Returns true if this manager is called while there 26.       is another call to itself pending. 27.       @return true if there are multiple calls to this manager 28.    */ 29.    public boolean inSameManager() 30.    { 31.       Class[] cc = getClassContext(); 32. 33.       // skip past current set of calls to this manager 34.       int i = 0; 35.       while (i < cc.length && cc[0] == cc[i]) 36.          i++; 37. 38.       // check if there is another call to this manager 39.       while (i < cc.length) 40.       { 41.          if (cc[0] == cc[i]) return true; 42.          i++; 43.       } 44.       return false; 45.    } 46. 47.    /** 48.       Checks if a file contains bad words. 49.       @param fileName the name of the file 50.       @return true if the file name ends with .txt and it 51.       contains at least one bad word. 52.    */ 53.    boolean containsBadWords(String fileName) 54.    { 55.       if (!fileName.toLowerCase().endsWith(".txt")) return false; 56.          // only check text files 57.       Scanner in; 58.       try 59.       { 60.          in = new Scanner(new FileReader(fileName)); 61.       } 62.       catch (IOException e) 63.       { 64.          return false; 65.       } 66.       while (in.hasNext()) 67.          if (badWords.contains(in.next().toLowerCase())) 68.          { 69.             in.close(); 70.             System.out.println(fileName); 71.             return true; 72.          } 73.       return false; 74.    } 75. 76.    private List badWords = Arrays.asList(new String[] { "sex", "drugs", "c++" }); 77. } 



 java.lang.System 1.0 

  • void setSecurityManager(SecurityManager s)

    sets the security manager for the remainder of this application. If s is null, no action is taken. This method throws a security exception if the current security manager does not permit the installation of a new security manager.

  • SecurityManager getSecurityManager()

    gets the system security manager; returns null if none is installed.


 java.lang.SecurityManager 1.0 

  • Class[] getClassContext() 1.1

    returns an array of the classes for the currently executing methods. The element at position 0 is the class of the currently running method, the element at position 1 is the class of the caller of the current method, and so on. Only the class names, not the method names, are available.

User Authentication

Starting with JDK 1.4, the Java Authentication and Authorization Service (JAAS) is included in the Java 2 Standard Edition. The "authentication" part is concerned with ascertaining the identity of a program user. The "authorization" part maps users to permissions.

JAAS is a "pluggable" API that isolates Java applications from the particular technology used to implement authentication. It supports, among others, UNIX logins, NT logins, Kerberos authentication, and certificate-based authentication.

Once a user has been authenticated, you can attach a set of permissions. For example, here we grant Harry a particular set of permissions that other users do not have. The syntax is like this:

 grant principal com.sun.security.auth.UnixPrincipal "harry" {    permission java.util.PropertyPermission "user.*", "read";    . . . }; 

The com.sun.security.auth.UnixPrincipal class checks the name of the UNIX user who is running this program. Its getName method returns the UNIX login name, and we check whether that name equals "harry".

You use a LoginContext to allow the security manager to check such a grant statement. Here is the basic outline of the login code:

 try {    System.setSecurityManager(new SecurityManager());    LoginContext context = new LoginContext("Login1"); // defined in JAAS configuration file    context.login();    // get the authenticated Subject    Subject subject = context.getSubject();    . . .    context.logout(); } catch (LoginException exception) // thrown if login was not successful {    exception.printStackTrace(); } 

Now the subject denotes the individual who has been authenticated.

The string parameter "Login1" in the LoginContext constructor refers to an entry with the same name in the JAAS configuration file. Here is a sample configuration file:

 Login1 {    com.sun.security.auth.module.UnixLoginModule required;    com.whizzbang.auth.module.RetinaScanModule sufficient; }; Login2 {    . . . }; 

Of course, the JDK contains no biometric login modules. The JDK contains the following modules in the com.sun.security.auth.module package:

 UnixLoginModule NTLoginModule Krb5LoginModule JndiLoginModule KeyStoreLoginModule 

A login policy consists of a sequence of login modules, each of which is labeled required, sufficient, requisite, or optional. The modules are executed in turn, until a sufficient module succeeds, a requisite module fails, or the end of the module list is reached. Authentication is successful if all required and requisite modules succeed, or if none of them were executed, if at least one sufficient or optional module succeeds.

A login authenticates a subject, which can have multiple principals. A principal describes some property of the subject, such as the user name, group ID, or role. As you saw in the grant statement, principals govern permissions. The com.sun.security.auth.UnixPrincipal describes the UNIX login name, and the UnixNumericGroupPrincipal can test for membership in a UNIX group.

A grant clause can test for a principal, with the syntax


grant principalClass "principalName"

For example:

 grant com.sun.security.auth.UnixPrincipal "harry" 

When a user has logged in, you then run, in a separate access control context, the code that requires checking of principals. Use the static doAs or doAsPrivileged method to start a new PrivilegedAction whose run method executes the code.

Both of those methods execute an action by calling the run method of an object that implements the PrivilegedAction interface, using the permissions of the subject's principals:

 PrivilegedAction action = new    PrivilegedAction()    {       public Object run()       {         // run with permissions of subject principals          . . .       }    }; Subject.doAsPrivileged(subject, action, null); // or doAs(subject, action) 

If the actions can throw checked exceptions, then you implement the PrivilegedExceptionAction interface instead.

The difference between the doAs and doAsPrivileged methods is subtle. The doAs method starts out with the current access control context, whereas the doAsPrivileged method starts out with a new context. The latter method allows you to separate the permissions for the login code and the "business logic." In our example application, the login code has permissions

 permission javax.security.auth.AuthPermission "createLoginContext.Login1"; permission javax.security.auth.AuthPermission "doAsPrivileged"; 

The authenticated user has a permission

 permission java.util.PropertyPermission "user.*", "read"; 

If we had used doAs instead of doAsPrivileged, then the login code would have also needed that permission!

The program in Example 9-8 and Example 9-9 demonstrates how to restrict permissions to certain users. The AuthTest program authenticates a user and then runs a simple action that retrieves a system property.

To make this example work, package the code for the login and the action into two separate JAR files:

 javac AuthTest.java jar cvf login.jar AuthTest*.class javac SysPropAction.java jar cvf action.jar SysPropAction.class 

If you look at the policy file in Example 9-10, you will see that the UNIX user with the name harry has the permission to read all files. Change harry to your login name. Then run the command

 java -classpath login.jar:action.jar    -Djava.security.policy=AuthTest.policy    -Djava.security.auth.login.config=jaas.config    AuthTest 

Example 9-11 shows the login configuration.

On Windows, change Unix to NT in both AuthTest.policy and jaas.config, and use a semicolon to separate the JAR files:

 java -classpath login.jar;action.jar . . . 

The AuthTest program should now display the value of the user.home property. However, if you change the login name in the AuthTest.policy file, then a security exception should be thrown because you no longer have the required permission.

CAUTION

Be careful to follow these instructions exactly. It is very easy to get the setup wrong by making seemingly innocuous changes.


Example 9-8. AuthTest.java

[View full width]

  1. import java.security.*;  2. import javax.security.auth.*;  3. import javax.security.auth.login.*;  4.  5. /**  6.    This program authenticates a user via a custom login and then executes the  SysPropAction  7.    with the user's privileges.  8. */  9. public class AuthTest 10. { 11.    public static void main(final String[] args) 12.    { 13.       try 14.       { 15.          System.setSecurityManager(new SecurityManager()); 16.          LoginContext context = new LoginContext("Login1"); 17.          context.login(); 18.          System.out.println("Authentication successful."); 19.          Subject subject = context.getSubject(); 20.          System.out.println("subject=" + subject); 21.          PrivilegedAction action = new SysPropAction("user.home"); 22.          Object result = Subject.doAsPrivileged(subject, action, null); 23.          System.out.println(result); 24.          context.logout(); 25.       } 26.       catch (LoginException e) 27.       { 28.          e.printStackTrace(); 29.       } 30.    } 31. } 

Example 9-9. SysPropAction.java
  1. import java.security.*;  2.  3. /**  4.    This action looks up a system property.  5. */  6. public class SysPropAction implements PrivilegedAction  7. {  8.    /**  9.       Constructs an action for looking up a given property. 10.       @param propertyName the property name (such as "user.home") 11.    */ 12.    public SysPropAction(String propertyName) { this.propertyName = propertyName; } 13. 14.    public Object run() 15.    { 16.       return System.getProperty(propertyName); 17.    } 18. 19.    private String propertyName; 20. } 

Example 9-10. AuthTest.policy
  1. grant codebase "file:login.jar"  2. {  3.    permission javax.security.auth.AuthPermission "createLoginContext.Login1";  4.    permission javax.security.auth.AuthPermission "doAsPrivileged";  5. };  6.  7. grant principal com.sun.security.auth.UnixPrincipal "harry"  8. {  9.    permission java.util.PropertyPermission "user.*", "read"; 10. }; 


 javax.security.auth.login.LoginContext 1.4 

  • LoginContext(String name)

    constructs a login context. The name corresponds to the login descriptor in the JAAS configuration file.

  • void login()

    establishes a login or throws LoginException if the login failed. Invokes the login method on the managers in the JAAS configuration file.

  • void logout()

    logs out the subject. Invokes the logout method on the managers in the JAAS configuration file.

  • Subject getSubject()

    returns the authenticated subject.


 javax.security.auth.Subject 1.4 

  • Set<Principal> getPrincipals()

    gets the principals of this subject.

  • static Object doAs(Subject subject, PrivilegedAction action)

  • static Object doAs(Subject subject, PrivilegedExceptionAction action)

  • static Object doAsPrivileged(Subject subject, PrivilegedAction action, AccessControlContext context)

  • static Object doAsPrivileged(Subject subject, PrivilegedExceptionAction action, AccessControlContext context)

    execute the privileged action on behalf of the subject. Return the return value of the run method. The doAsPrivileged methods execute the action in the given access control context. You can supply a "context snapshot" that you obtained earlier by calling the static method AccessController.getContext(), or you can supply null to execute the code in a new context.


 java.security.PrivilegedAction 1.4 

  • Object run()

    You must define this method to execute the code that you want to have executed on behalf of a subject.


 java.security.PrivilegedExceptionAction 1.4 

  • Object run()

    You must define this method to execute the code that you want to have executed on behalf of a subject. This method may throw any checked exceptions.


 java.security.Principal 1.1 

  • String getName()

    returns the identifying name of this principal.

JAAS Login Modules

In this section, we look at a JAAS example that shows you

  • How to implement your own login module, and

  • How to implement role-based authentication.

Supplying your own login module is useful if you store login information in a database. Even if you are happy with the default module, studying a custom module will help you understand the JAAS configuration file options.

Role-based authentication is essential if you manage a large number of users. It would be impractical to put the names of all legitimate users into a policy file. Instead, the login module should map users to roles such as "admin" or "HR," and the permissions should be based on these roles.

One job of the login module is to populate the principal set of the subject that is being authenticated. If a login module supports roles, it adds Principal objects that describe roles. The JDK does not provide a class for this purpose, so we wrote our own (see Example 9-12). The class simply stores a description/value pair, such as role=admin. Its getName method returns that pair, so we can add role-based permissions into a policy file:

 grant principal SimplePrincipal "role=admin" { . . . } 

Our login module (see Example 9-13) looks up users, passwords, and roles in a text file that contains lines like this:

 harry|wombat|admin carl|mockturtle|HR 

Of course, in a realistic login module, you would store this information in a database or directory.

You can find the code for the SimpleLoginModule in Example 9-13. The checkLogin method checks whether the user name and password match a user record in the password file. If so, we add two SimplePrincipal objects to the subject's principal set:

 Set<Principal> principals = subject.getPrincipals(); principals.add(new SimplePrincipal("username", username)); principals.add(new SimplePrincipal("role", role)); 

The remainder of SimpleLoginModule is straightforward plumbing. The initialize method receives

  • The Subject that is being authenticated;

  • A handler to retrieve login information;

  • A sharedState map that can be used for communication between login modules; and

  • An options map that contains name/value pairs that are set in the login configuration.

For example, we configure our module as follows:

 SimpleLoginModule required pwfile="password.txt"; 

The login module retrieves the pwfile settings from the options map.

The login module does not gather the user name and password; that is the job of a separate handler. This separation allows you to use the same login module without worrying whether the login information comes from a GUI dialog, a console prompt, or a configuration file.

The handler is specified when you construct the LoginContext, for example,

 LoginContext context = new LoginContext("Login1",    new com.sun.security.auth.callback.DialogCallbackHandler()); 

The DialogCallbackHandler pops up a simple GUI dialog box to retrieve the user name and password. com.sun.security.auth.callback.TextCallbackHandler gets the information from the console.

However, in our application, we have our own GUI for collecting the user name and password (see Figure 9-9). We produce a simple handler that merely stores and returns that information (see Example 9-14).

Figure 9-9. A custom login module


The handler has a single method, handle, that processes an array of Callback objects. A number of predefined classes, such as NameCallback and PasswordCallback, implement the Callback interface. You could also add your own class, such as RetinaScanCallback. The handler code is a bit unsightly because it needs to analyze the types of the callback objects:

 public void handle(Callback[] callbacks) {    for (Callback callback : callbacks)    {       if (callback instanceof NameCallback) . . .       else if (callback instanceof PasswordCallback) . . .       else . . .    } } 

The login module prepares an array of the callbacks that it needs for authentication:

 NameCallback nameCall = new NameCallback("username: "); PasswordCallback passCall = new PasswordCallback("password: ", false); callbackHandler.handle(new Callback[] { nameCall, passCall }); 

Then it retrieves the information from the callbacks.

The program in Example 9-11 displays a form for entering the login information and the name of a system property. If the user is authenticated, the property value is retrieved in a PrivilegedAction. As you can see from the policy file in Example 9-15, only users with the admin role have permission to read properties.

As in the preceding section, you must separate the login and action code. Create two JAR files:

 javac *.java jar cvf login.jar JAAS*.class Simple*.class jar cvf action.jar SysPropAction.class 

Then run the program as

 java -classpath login.jar:action.jar    -Djava.security.policy=JAASTest.policy    -Djava.security.auth.login.config=jaas.config    JAASTest 

Example 9-16 shows the login configuration.

It is possible to support a more complex two-phase protocol, whereby a login is committed if all modules in the login configuration were successful. For more information, see the login module developer's guide at http://java.sun.com/j2se/5.0/docs/guide/security/jaas/JAASLMDevGuide.html.

Example 9-11. JAASTest.java

[View full width]

  1. import java.awt.*;  2. import java.awt.event.*;  3. import javax.security.auth.*;  4. import javax.security.auth.login.*;  5. import javax.swing.*;  6.  7. /**  8.    This program authenticates a user via a custom login and then executes the  SysPropAction  9.    with the user's privileges. 10. */ 11. public class JAASTest 12. { 13.    public static void main(final String[] args) 14.    { 15.       System.setSecurityManager(new SecurityManager()); 16.       JFrame frame = new JAASFrame(); 17.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 18.       frame.setVisible(true); 19.    } 20. } 21. 22. /** 23.    This frame has text fields for user name and password, a field for the name of the  requested 24.    system property, and a field to show the property value. 25. */ 26. class JAASFrame extends JFrame 27. { 28.    public JAASFrame() 29.    { 30.       setTitle("JAASTest"); 31. 32.       username = new JTextField(20); 33.       password = new JPasswordField(20); 34.       propertyName = new JTextField(20); 35.       propertyValue = new JTextField(20); 36.       propertyValue.setEditable(false); 37. 38.       JPanel panel = new JPanel(); 39.       panel.setLayout(new GridLayout(0, 2)); 40.       panel.add(new JLabel("username:")); 41.       panel.add(username); 42.       panel.add(new JLabel("password:")); 43.       panel.add(password); 44.       panel.add(propertyName); 45.       panel.add(propertyValue); 46.       add(panel, BorderLayout.CENTER); 47. 48.       JButton getValueButton = new JButton("Get Value"); 49.       getValueButton.addActionListener(new 50.          ActionListener() 51.          { 52.             public void actionPerformed(ActionEvent event) { getValue(); } 53.          }); 54.       JPanel buttonPanel = new JPanel(); 55.       buttonPanel.add(getValueButton); 56.       add(buttonPanel, BorderLayout.SOUTH); 57.       pack(); 58.    } 59. 60.    public void getValue() 61.    { 62.       try 63.       { 64.          LoginContext context = new LoginContext("Login1", 65.             new SimpleCallbackHandler(username.getText(), password.getPassword())); 66.          context.login(); 67.          Subject subject = context.getSubject(); 68.          propertyValue.setText( 69.             "" + Subject.doAsPrivileged(subject, new SysPropAction(propertyName .getText()), null)); 70.          context.logout(); 71.       } 72.       catch (LoginException e ) 73.       { 74.          JOptionPane.showMessageDialog(this, e); 75.       } 76.    } 77. 78.    private JTextField username; 79.    private JPasswordField password; 80.    private JTextField propertyName; 81.    private JTextField propertyValue; 82. } 

Example 9-12. SimplePrincipal.java

[View full width]

  1. import java.security.*;  2.  3. /**  4.    A principal with a named value (such as "role=HR" or "username=harry").  5. */  6. public class SimplePrincipal implements Principal  7. {  8.    /**  9.       Constructs a SimplePrincipal to hold a description and a value. 10.       @param roleName the role name 11.    */ 12.    public SimplePrincipal(String descr, String value) { this.descr = descr; this.value  = value; } 13. 14.    /** 15.       Returns the role name of this principal 16.       @return the role name 17.    */ 18.    public String getName() { return descr + "=" + value; } 19. 20.    public boolean equals(Object otherObject) 21.    { 22.       if (this == otherObject) return true; 23.       if (otherObject == null) return false; 24.       if (getClass() != otherObject.getClass()) return false; 25.       SimplePrincipal other = (SimplePrincipal) otherObject; 26.       return getName().equals(other.getName()); 27.    } 28.    public int hashCode() { return getName().hashCode(); } 29. 30.    private String descr; 31.    private String value; 32. } 

Example 9-13. SimpleLoginModule.java

[View full width]

  1. import java.io.*;  2. import java.lang.reflect.*;  3. import java.security.*;  4. import java.util.*;  5. import javax.security.auth.*;  6. import javax.security.auth.login.*;  7. import javax.security.auth.callback.*;  8. import javax.security.auth.spi.*;  9. 10. import javax.swing.*; 11. 12. /** 13.    This login module authenticates users by reading usernames, passwords, and roles 14.    from a text file. 15. */ 16. public class SimpleLoginModule implements LoginModule 17. { 18.    public void initialize(Subject subject, CallbackHandler callbackHandler, 19.       Map<String, ?> sharedState, Map<String, ?> options) 20.    { 21.       this.subject = subject; 22.       this.callbackHandler = callbackHandler; 23.       this.sharedState = sharedState; 24.       this.options = options; 25.    } 26. 27.    public boolean login() throws LoginException 28.    { 29.       if (callbackHandler == null) 30.          throw new LoginException("no handler"); 31. 32.       NameCallback nameCall = new NameCallback("username: "); 33.       PasswordCallback passCall = new PasswordCallback("password: ", false); 34.       try 35.       { 36.          callbackHandler.handle(new Callback[] { nameCall, passCall }); 37.       } 38.       catch (UnsupportedCallbackException e) 39.       { 40.          LoginException e2 = new LoginException("Unsupported callback"); 41.          e2.initCause(e); 42.          throw e2; 43.       } 44.       catch (IOException e) 45.       { 46.          LoginException e2 = new LoginException("I/O exception in callback"); 47.          e2.initCause(e); 48.          throw e2; 49.       } 50. 51.       return checkLogin(nameCall.getName(), passCall.getPassword()); 52.    } 53. 54.    /** 55.       Checks whether the authentication information is valid. If it is, the subject  acquires 56.       principals for the user name and role. 57.       @param username the user name 58.       @param password a character array containing the password 59.       @return true if the authentication information is valid 60.    */ 61.    private boolean checkLogin(String username, char[] password) throws LoginException 62.    { 63.       try 64.       { 65.          Scanner in = new Scanner(new FileReader("" + options.get("pwfile"))); 66.          while (in.hasNextLine()) 67.          { 68.             String[] inputs = in.nextLine().split("\\|"); 69.             if (inputs[0].equals(username) && Arrays.equals(inputs[1].toCharArray(),  password)) 70.             { 71.                String role = inputs[2]; 72.                Set<Principal> principals = subject.getPrincipals(); 73.                principals.add(new SimplePrincipal("username", username)); 74.                principals.add(new SimplePrincipal("role", role)); 75.                return true; 76.             } 77.          } 78.          in.close(); 79.          return false; 80.       } 81.       catch (IOException e) 82.       { 83.          LoginException e2 = new LoginException("Can't open password file"); 84.          e2.initCause(e); 85.          throw e2; 86.       } 87.    } 88. 89.    public boolean logout() { return true; } 90.    public boolean abort() { return true; } 91.    public boolean commit() { return true; } 92. 93.    private Subject subject; 94.    private CallbackHandler callbackHandler; 95.    private Map<String, ?> sharedState; 96.    private Map<String, ?> options; 97. } 

Example 9-14. SimpleCallbackHandler.java
  1. import javax.security.auth.callback.*;  2.  3. /**  4.    This simple callback handler presents the given user name and password.  5. */  6. public class SimpleCallbackHandler implements CallbackHandler  7. {  8.    /**  9.       Constructs the callback handler. 10.       @param username the user name 11.       @param password a character array containing the password 12.    */ 13.    public SimpleCallbackHandler(String username, char[] password) 14.    { 15.       this.username = username; 16.       this.password = password; 17.    } 18. 19.    public void handle(Callback[] callbacks) 20.    { 21.       for (Callback callback : callbacks) 22.       { 23.          if (callback instanceof NameCallback) 24.          { 25.             ((NameCallback) callback).setName(username); 26.          } 27.          else if (callback instanceof PasswordCallback) 28.          { 29.             ((PasswordCallback) callback).setPassword(password); 30.          } 31.       } 32.    } 33. 34.    private String username; 35.    private char[] password; 36. } 

Example 9-15. JAASTest.policy
  1. grant codebase "file:login.jar"  2. {  3.    permission java.awt.AWTPermission "showWindowWithoutWarningBanner";  4.    permission javax.security.auth.AuthPermission "createLoginContext.Login1";  5.    permission javax.security.auth.AuthPermission "doAsPrivileged";  6.    permission javax.security.auth.AuthPermission "modifyPrincipals";  7.    permission java.io.FilePermission "password.txt", "read";  8. };  9. 10. grant principal SimplePrincipal "role=admin" 11. { 12.    permission java.util.PropertyPermission "*", "read"; 13. }; 

Example 9-16. jaas.config
  1. Login1  2. {  3.    SimpleLoginModule required pwfile="password.txt";  4. }; 



 javax.security.auth.callback.CallbackHandler 1.4 

  • void handle(Callback[] callbacks)

    handles the given callbacks, interacting with the user if desired, and stores the security information in the callback objects.




 javax.security.auth.callback.NameCallback 1.4 

  • NameCallback(String prompt)

  • NameCallback(String prompt, String defaultName)

    construct a NameCallback with the given prompt and default name.

  • void setName(String name)

  • String getName()

    set or get the name gathered by this callback.

  • String getPrompt()

    gets the prompt to use when querying this name.

  • String getDefaultName()

    gets the default name to use when querying this name.


 javax.security.auth.callback.PasswordCallback 1.4 

  • PasswordCallback(String prompt, boolean echoOn)

    constructs a PasswordCallback with the given prompt and echo flag.

  • void setPassword(char[] password)

  • char[] getPassword()

    set or get the password gathered by this callback.

  • String getPrompt()

    gets the prompt to use when querying this password.

  • boolean isEchoOn()

    gets the echo flag to use when querying this password.


 javax.security.auth.spi.LoginModule 1.4 

  • void initialize(Subject subject, CallbackHandler handler, Map<String,?> sharedState, Map<String,?> options)

    initializes this LoginModule for authenticating the given subject. During login processing, use the given handler to gather login information. Use the sharedState map for communication with other login modules. The options map contains the name/value pairs specified in the login configuration for this module instance.

  • boolean login()

    carries out the authentication process and populate the subject's principals. Returns true if the login was successful.

  • boolean commit()

    is called after all login modules were successful, for login scenarios that require a two-phase commit. Returns true if the operation was successful.

  • boolean abort()

    is called if the failure of another login module caused the login process to abort. Returns true if the operation was successful.

  • boolean logout()

    logs out this subject. Returns true if the operation was successful.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net