RMI and Access Control

Can we use JAAS for user authentication and access control of operations in conjunction with RMI? How can we do what we did in the JAAS Enabled Sample Application section of Chapter 5, Access Control, to add user authentication and action authorization capability to the RMI-based sample application?

This is possible but non-trivial. The base RMI architecture was developed before JAAS came into existence and hasn't been upgraded to honor JAAS. It is still possible to write RMI client and server programs so that the user credentials (username and password) are collected at the client and passed to the server for authentication. The server goes through the authentication process using JAAS and initializes the Subject instance corresponding to the authenticated user. Each subsequent call from the same client has to be executed as a PrivilegedAction or PrivilegedExceptionAction, invoked through Subject.doAs() method, passing the appropriate Subject instance as an argument.

What does it mean to a programmer in terms of program code? There must be an initial handshake between the client and the server to perform the authentication. During this process, the server can create a unique token, store the Subject instance in a hash table indexed by this token and return the token to the client. The client will have to pass this token as an argument to every subsequent call, so that the server is able to retrieve the appropriate Subject instance. The downside to this approach is that the method signature will have to change to accommodate the extra argument and additional code will have to be written to manage this token, both at the server end and the client end. Not only that, if anyone got hold of the token or is able to guess it, then that person can easily impersonate the client.

Another alternative is to have a central authenticator object responsible for authenticating the client and returning a remote reference of a newly created object for further interaction. The Subject instance can be stored as a member field of this object and its methods can be used to intercept the calls to the real object. In fact, in our sample application we already have these interceptor classes such as RemoteBankImpl, RemoteAccountImpl and so on. Currently, each method of this class simply forwards the call to the underlying BankIntf, or AccountIntf object. We can do the extra processing there and wrap the method invocation to the underlying object within a privileged block. This approach has the advantage of not changing the client code significantly and even the server-side changes are minimal.

Let us apply these ideas to the RMI sample application and enable it with JAAS-based access control. We copy the contents of ch8ex1 directory to ch8ex4 and do the subsequent development there.

The steps involved in this development are:

1.
Write a remote interface common.RemoteLoginServer for the authenticator and also the corresponding implementation class server.RemoteLoginServerImpl. This interface has only one method: login(). It takes a username and password as argument, performs the authentication, creates a RemoteBankImpl instance on successful authentication, and returns it to the client.

2.
Modify the main() method of the server program RemoteBankServer to register the RemoteLoginServerImpl instance.

3.
Modify the client class client.RMIBCShell to prompt the user for username and password, look up the authenticator and invoke login() method on this remote reference, passing the username and the password as arguments.

4.
Modify the individual methods of implementation classes to use the privileged block of code.

5.
Setup the login configuration file and policy file in the same way as the JAAS-enabled sample application in Chapter 5.

6.
Compile the source files and execute the application.

Let us go over each of these steps in detail.

Processing Client Logins

The remote interface RemoteLoginServer defines the contract for the entity responsible for user authentication.

// File: srcjsbookch8ex4commonRemoteLoginServer.java
package common;
import javax.security.auth.login.LoginException;

public interface RemoteLoginServer extends java.rmi.Remote {
  public RemoteBank login(String username, String password)
      throws LoginException, java.rmi.RemoteException;
}

For better security, we should use char[] in place of String for storing the password. However, that increases code complexity. So we continue to use String, keeping in mind that this should be char[] in a high security production code.

The class RemoteLoginServerImpl implements this remote interface. Its source code is shown in Listing 8-7.

Listing 8-7. The authenticator class RemoteLoginServerImpl
// File: srcjsbookch8ex4serverRemoteLoginServerImpl.java
package server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import org.jstk.example.bank.BankIntf;
import common.RemoteBank;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

public class RemoteLoginServerImpl extends UnicastRemoteObject
    implements common.RemoteLoginServer {
  private BankIntf bi;
  public RemoteLoginServerImpl(BankIntf bi) throws RemoteException {
    this.bi = bi;
  }
  public common.RemoteBank login(String username, String password)
      throws LoginException, RemoteException {
    LoginContext lc = new LoginContext("Bank",
          new RemoteCallbackHandler(username, password));
    lc.login();
    return new RemoteBankImpl(bi, lc.getSubject());
  }
}

Pay attention to the class RemoteCallbackHandler. It is used as LoginContext constructor argument, accepting the username and the password as constructor arguments and filling the Callback objects on handle(Callback[]) invocation with these values. Other handlers we have seen so far prompted the user to get these values.

One other thing to notice is that login() method creates a RemoteBankImpl object with the Subject instance as a constructor argument. Access to the Subject instance is required for executing the real operation within the security context of the user represented by this Subject instance. In fact, all the remote classes have been modified to store reference to Subject instance and initialize it in the constructor.

Modified main() of the Client and the Server

A client must go through the RemoteLoginServer for getting a remote reference to the interface RemoteBank. This requires modifying both the server program RemoteBankServer.java and the client program RMIBCShell.java. Here is the relevant server code:

RemoteLoginServerImpl rlsi = new RemoteLoginServerImpl(bank);
Naming.rebind("MyRemoteLoginServer", rlsi);

Here is the client code to locate this remote reference and perform login:

RemoteLoginServer rls = (RemoteLoginServer)Naming.lookup("rmi://"
    + args[0] + "/" + "MyRemoteLoginServer");
RemoteBank rbank = null;
try {
  rbank = rls.login(username, password);
} catch (LoginException le){
  System.out.println("Login Failed. " + le.getMessage());
  return;
}

The code to prompt the user for username and password is not shown here. Consult the electronic version of the source code packaged within JSTK for the complete listing.

Intercepting the Method Invocations

The best way to describe this mechanism is to look at the source code of one of the methods that intercepts an invocation and executes the real method in the security context of the logged-in user.

Listing 8-8. Executing code within security context of logged-in user
// From RemoteServerImpl class
public common.RemoteAccount openAccount(java.math.BigDecimal
    initialDeposit) throws java.rmi.RemoteException {
  final BigDecimal iDf = initialDeposit;
  final BankIntf bif = bi;
  final Subject subf = sub;
  RemoteAccount rA = null;
  try {
    rA = (common.RemoteAccount)Subject.doAs(sub,
      new PrivilegedExceptionAction() {
        public Object run() throws Exception {
          return new RemoteAccountImpl(bif.openAccount(iDf), subf);
        }
      });
  } catch (PrivilegedActionException pae){
    if (pae.getException() instanceof RemoteException)
      throw (RemoteException)pae.getException();
  }
  return rA;
}

You notice that the member variables have been assigned to the local final variables of the same type. This is so they can be accessed within the anonymous inner class. Another interesting thing to note is the treatment of exceptions. The static method doAs() wraps checked exceptions within a PrivilegedActionException. This must be retrieved and re-thrown. All remote methods need to be modified in a similar fashion.

Login Configuration and Policy Files

We use a login configuration file similar to the one used for JAAS-enabled sample application.

// file: srcjsbookch8ex4login.conf
Bank {
  org.jstk.uam.JSTKLoginModule required uamfile="uamdb.ser";
};

This essentially says that use JSTKLoginModule for authentication and pass file uamdb.ser in the current directory as the datastore for user account information. Recall that we developed the login module class JSTKLoginModule in Chapter 5, Access Control.

The policy file is shown in Listing 8-9 and is similar to the one used for JAAS enabled sample application.

Listing 8-9. Policy file for JAAS based access control
// file: srcjsbookch8ex4ank.policy
grant codeBase "file:${user.dir}/server_prv.jar" {
  permission java.net.SocketPermission "localhost:1099",
      "connect, resolve";
  permission java.net.SocketPermission "192.168.1.100:1024-",
      "connect, accept, resolve";
  permission java.util.PropertyPermission "*", "read, write";
  permission javax.security.auth.AuthPermission "createLoginContext";
  permission javax.security.auth.AuthPermission "doAs";
  permission java.io.FilePermission "${user.dir}${/}*", "read";
  permission org.jstk.example.bank.server.BankPermission "*",
    "open, close, get, list";
  permission org.jstk.example.bank.server.AccountPermission "*",
    "open, close, withdraw, deposit, read";
};

grant {
  permission java.io.FilePermission "${user.dir}${/}*", "read, write";
  permission javax.security.auth.AuthPermission "modifyPrincipals";
};

grant Principal org.jstk.uam.JSTKRolePrincipal "admin" {
    permission org.jstk.example.bank.server.BankPermission "*",
      "open, close, get, list";
    permission org.jstk.example.bank.server.AccountPermission "*",
      "open, close, deposit, read";
};

grant Principal org.jstk.uam.JSTKUserPrincipal "pankaj" {
  permission org.jstk.example.bank.server.AccountPermission "1000",
    "withdraw, deposit, read";
  permission org.jstk.example.bank.server.BankPermission "1000", "get";
};

The implementation classes for remote interfaces and the JSTKLoginModule related classes are packaged within server_prv.jar file and have been assigned larger privileges than the other portions of the code. This is required for the permission checks to succeed from methods invoked from methods in these classes. Server side classes from the base sample application, those within package org.jstk.example.bank.server, are left within server.jar. Contents of other jar files is not changed.

Running the Application

We are now ready to compile and run the application. As with other modifications of the sample application, use the script file comp.bat to compile the sources and create the jar files. After compiling the sources, run the RMI Registry:

C:ch8ex4>rmiregistry
						

Next, run the server program in a different window:

C:ch8ex4>java –cp server_prv.jar;server.jar;common.jar 
-Djava.security.manager -Djava.security.policy=bank.policy 
-Djava.security.auth.login.config=login.conf 
							server.RemoteBankServer
RemoteBank Server with LoginServer ready.

Finally, run the client program, assuming the same user account information as the one used for the JAAS-enabled sample application of Chapter 5, Access Control. Remember that the user veena has the admin role and the user pankaj is a normal user. The policy file bank.policy gives the permission to open accounts only to users with the admin role. User pankaj has permission to access the account with account number 1000:

C:ch8ex4>java –cp 
							client.jar;common.jar;server_stub.jar;server.jar 
client.RMIBCShell localhost
login: veena
password: veena
rbcsh>open 200.00
Account Opened: 1000
rbcsh>open 2000.00
Account Opened: 1001
rbcsh>quit

Let us try these accounts after logging-in as pankaj.

C:ch8ex4>java –cp client.jar;common.jar;server_stub.jar;server.jar 
client.RMIBCShell localhost
login: pankaj
password: pankaj
rbcsh>get 1001
Access denied
rbcsh>get 1000
Current Account: 1000
rbcsh>deposit 10.00
Deposited: 10.00
rbcsh>withdraw 2000.00
Not Enough Funds
rbcsh>quit

See how user pankaj is able to access the account with number 1000 but not the account with number 1001. This is the same behavior that we had observed with the single JVM sample application with access control. Even the error handling based on exceptions work seamlessly!

Conclusions

Though we got the RMI and JAAS working together pretty much the same way JAAS worked for a single JVM program, we had to write a lot of code and perform complicated setup. Luckily for us, we already had the placeholders for intercepting the remote method invocations and performing the necessary operations for running the target methods in the context of the authenticated client. Also, the client located only one remote reference, RemoteBank, through the RMI Registry.

Applying JAAS to a real application with lots of remote interfaces and complex interaction patterns could mean a significant amount of redesign and additional code. A better solution would be to use a framework that abstracts out the common behavior in the framework code. Such frameworks exist for RMI. One such open source framework, named SmartRMI, can be found at http://sourceforge.net/projects/smartrmi. Conceptually, this framework is quite similar to the sample application illustrated in this chapter but it minimizes proliferation of interfaces and classes, and explicit coding, by use of dynamic proxies. Dynamic Proxies are features of Java language introduced in J2SE v1.3 and refer to classes that can be created at runtime to implement a collection of interfaces. A method invocation on proxy instance results into a generic method invocation on a handler, supplied at the time of proxy instance creation. This handler can do pre-processing before calling the appropriate method on the target object and/or post-processing after the method has returned.

Other thing to keep in mind is that we have applied different security solutions—security manager as a safeguard against downloaded code, SSL for transport security, and JAAS for user authentication and access control—in isolation. A real application would require two or more of these to be used together. Modification of the sample application to combine multiple security solutions is left as an exercise to the reader.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.29