Performing programmatic authorization with JAAS

Authorization

Authorization is about allowing or denying access to resources to a particular subject (a user, a group, a company, …). When a subject is authenticated, it is augmented with one or more principals that identify the subject for one or more resources, for example a social security number for one resource or a role of an administrator for another. A subject can also have credentials associated with them, any Java objects that contains security-related information about the subject, for example a certificate or a password.

To go ahead with this example, first read the authentication example.

In the following example, the authentication example will be augmented with a section that is only executed when it is permitted to do so by a particular principal, in our example “johndoe”. As opposed to specifying the principals and permissions in a policy file (see How do I use authorization with JAAS (declarative)) it is done programmatically.


We also have another policy file that grants permissions to read and write System properties (needed by the Swing DialogCallbackHandler), to create a LoginContext (necessary for authentication), to execute a doAsPrivileged method (necessary for executing sensitive code that requires principal permissions) and to modify principals (necessary when we add a principal to the subject). jaasmain.policy:

grant {
   permission java.util.PropertyPermission "*", "read, write";
   permission javax.security.auth.AuthPermission 
                    "createLoginContext.Main";
   permission javax.security.auth.AuthPermission "doAsPrivileged";
   permission javax.security.auth.AuthPermission "modifyPrincipals";
};

Our module that encapsulates code to do authentication has not changed from the authentication example.

UsernamePasswordLoginModule.java:

import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.spi.*;
import javax.security.auth.*;
import java.security.*;
import java.util.*;
import java.io.*;
 
public class UsernamePasswordLoginModule implements LoginModule {
   private Subject subject;
   private CallbackHandler callbackHandler;
 
   private String username;
   private char[] password;
   private boolean loginSucceeded = false;
   private boolean commitSucceeded = false;
  
   private Principal principal;
 
   public void initialize(Subject subject, CallbackHandler callbackHandler, 
                          Map sharedState, Map options) {
      System.out.println("LoginModule initialize()");
      this.subject = subject;
      this.callbackHandler = callbackHandler;
      username = null;
      clearPassword();
      loginSucceeded = false;
      commitSucceeded = false;
   }
 
   public boolean login() throws LoginException {
      System.out.println("LoginModule login()");
      if (callbackHandler == null) {
         throw new LoginException("No CallbackHandler!");
      }
 
      Callback[] callbacks = new Callback[2];
      callbacks[0] = new NameCallback("Username: ");
      callbacks[1] = new PasswordCallback("Password: ", false);
 
      try {
         callbackHandler.handle(callbacks);
         username = ((NameCallback) callbacks[0]).getName();
         char[] temp = ((PasswordCallback) callbacks[1]).getPassword();
         password = new char[temp.length];
         System.arraycopy(temp, 0, password, 0, temp.length);
 
         BufferedReader br = new BufferedReader(new FileReader("passwd"));
         String line;
         while ((line = br.readLine()) != null) {
            int comma = line.indexOf(',');
            String un = line.substring(0, comma);
            String pw = line.substring(comma+1);
 
            if (username.equals(un) && new String(password).equals(pw)) {
               // succeeded!
               loginSucceeded = true;
               return true;
            }
         }
      }
      catch(IOException e) {
         throw new LoginException(e.toString());
      }
      catch(UnsupportedCallbackException e) {
         throw new LoginException(e.toString());
      }
 
      username = null;
      clearPassword();
      loginSucceeded = false;
 
      throw new FailedLoginException("Incorrect Username/Password");
   }
 
   public boolean commit() throws LoginException {
      System.out.println("LoginModule commit()");

      if (loginSucceeded == false) {
         return false;
      }
 
      principal = new MyPrincipal(username);
      if (!(subject.getPrincipals().contains(principal))) {
         subject.getPrincipals().add(principal);
      }
 
      username = null;
      clearPassword();
      commitSucceeded = true;
       
      return true;
   }
  
   public boolean abort() throws LoginException {
      System.out.println("LoginModule abort()");
 
      if (!loginSucceeded) {
         return false;
      }
      else if (loginSucceeded && commitSucceeded) {
         loginSucceeded = false;
         username = null;
         clearPassword();
         principal = null;
      }
      else {
         logout();
      }
 
      return true;
   }
 
   public boolean logout() throws LoginException {
      System.out.println("LoginModule logout()");
 
      subject.getPrincipals().remove(principal);
      loginSucceeded = false;
      commitSucceeded = false;
      username = null;
      clearPassword();
      principal = null;
 
      return true;
   }
 
   private void clearPassword() {
      if (password != null) {
         for (int i=0; i<password.length; i++) {
            password[i] = ' ';
         }
         password = null;
      }
   }
}

Our passwd “database” textfile has not changed from the authentication example.

passwd:

johndoe,sdefujm
janedoe,yuymndee

Our MyPrincipal class has also not changed from the authentication example.

MyPrincipal.java:

import java.security.*;
import java.io.*;
 
public class MyPrincipal implements Principal, Serializable
{
   private String name;

   public MyPrincipal(String name) {
      this.name = name;
   }
  
   public String getName() {
      return name;
   }
  
   public int hashCode() {
      return name.hashCode();
   }
 
   public String toString() {
      return getName();
   }
 
   public boolean equals(Object obj) {
      if (obj == null) {
         return false;
      }
 
      if (!(obj instanceof MyPrincipal)) {
         return false;
      }
 
      MyPrincipal mp = (MyPrincipal) obj;
      if (name.equals(mp.getName())) {
         return true;
      }
 
      return false;
   }
}

The code that is to be executed based on user authentication must be inside the run method of a class that implements java.security.PrivilegedAction.

WriteFileAction.java:

import java.security.PrivilegedAction;
import java.io.*;
 
public class WriteFileAction implements PrivilegedAction {
   public Object run() {
 
      try {
         BufferedWriter bw = new BufferedWriter(new FileWriter("c:\testfile"));
         bw.write("the shorter you live, the longer you're dead");
         bw.close();
         System.out.println("c:\testfile successfully written!");
      }
      catch(IOException e) {
         System.out.println(e);
      }
 
      return null;
   }
}

We want this code to be executed only when a specified principal is running it (“johndoe” as specified in the policy file). We enforce this by calling this code indirectly through the method doAs or doAsPrivileged. The difference between the two is described here.

Main.java:

import com.sun.security.auth.callback.*;
import javax.security.auth.login.*;
import javax.security.auth.*;
import java.security.*;
import java.util.*;
import java.io.*;
 
public class Main {
   public static void main(String []args) throws Exception { 
      try {
         LoginContext loginContext = new LoginContext("Main", 
                                             new DialogCallbackHandler());
 
         // will throw a LoginException if it fails, falls through otherwise
         loginContext.login();
 
         Subject subject = loginContext.getSubject();
         System.out.println(subject);
 
         PrivilegedAction action = new WriteFileAction();
         subject.doAsPrivileged(subject, action, null);
  
         loginContext.logout();
      }
      catch(LoginException e) {
         System.out.println("Unauthorized user!");
      } 
 
      // stop AWT thread (DialogCallbackHandler)
      System.exit(0);
   }
}

To run the code, you need to specify the policy files (or change the default java.policy one):

   c:jdk1.4binjava -Djava.security.auth.policy=accesscontrol.policy
                      -Djava.security.policy=jaasmain.policy
                      -Djava.security.auth.login.config==jaasmain.config
                      -Djava.security.manager
                      Main

Running the code with username=”johndoe”, password=”sdefujm” results in:

LoginModule initialize()
LoginModule login()
LoginModule commit()
Subject:
	Principal: johndoe

c:testfile successfully written!
LoginModule logout()

Running the code with username=”janedoe”, password=”yuymndee” (another authenticated user, but not authorized) results in:

LoginModule initialize()
LoginModule login()
LoginModule commit()
Subject:
	Principal: janedoe
Exception in thread "main" java.security.AccessControlException: access denied
java.io.FilePermission c:testfile write)
        at java.security.AccessControlContext.checkPermission(AccessControlCont
xt.java:273)
        at java.security.AccessController.checkPermission(AccessController.java
400)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:545)
        at java.lang.SecurityManager.checkWrite(SecurityManager.java:978)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:103)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:69)
        at java.io.FileWriter.<init>(FileWriter.java:44)
        at WriteFileAction.run(WriteFileAction.java:8)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAsPrivileged(Subject.java:436)
        at Main.main(Main.java:21)

Notice that “janedoe” is correctly authenticated, but not authorized to run the privileged code as that principal is not specified in the policy file accesscontrol.policy.