Item 63: Use role-based authorization



Item 63: Use role-based authorization

Authentication is pretty easy to recognize: it's the act of somebody attempting to prove his or her identity to the system, usually by presenting a password (a "secret") that theoretically only that person knows. The login form is a well-known idiom within Web applications, so we're not going to belabor the point here, although we will come back to it later.

Authorization, on the other hand, is a much more nebulous concept. Formally, authorization is the act of ascertaining what actions can be performed within the system. For example, under the normal Java Security Manager, authorization is granted to execute certain "sensitive" actions (such as starting threads, opening connections, and such) based on where the code was loaded from; this is well documented in books like Inside Java 2 Platform Security [Gong] and Java Security [Oaks]. Nowhere, however, within the standard Java security platform is the concept of user-based security discussed.

Most applications, however, have at least a coarse-grained idea of authorization buried within their requirements. Very few applications can treat all users exactly the same. At the very least there are usually three types of users: users, guests, and administrators. For many applications, the authorization model gets extremely complex: there may be dozens of different possible actions within the system and hundreds (if not thousands) of users, each of whom has different access rights to those actions. If we try to code this by hand, the presentation layer (whether servlet or JSP) will quickly become very complex.

Having all this security code laced all throughout the system is a crosscutting concern, something that really has nothing to do with the system's business logic. As a result, the J2EE specifications, most notably the servlet and EJB specifications, include configuration options within the deployment descriptor to try to remove this burden from the developer's shoulders.

To see how complex authorization can get in a real-world system, imagine for a moment a system with tens of thousands of users and hundreds of different unique access rights (not unusual for medium-sized applications). Every time a user is added to the system, a system administrator has to explicitly mark each access right for that user, which becomes more and more time-consuming as the number of access rights goes up. And heaven help your system administrators if you add new permissions to the system in a follow-up phase or release: now they have to go back through the entire user database and explicitly add that right to each one of those users that needs it (because you assume the default is that they don't have permission—see Item 60). This very quick way to make your system administrators cross with you does not lead to happy developer-administrator relationships.

One way to avoid this kind of administrative and developer security madness is to use a role-based authorization system, whereby permissions (rights to execute sensitive actions) are assigned to abstract categories of users called roles. The idea is to introduce a layer of indirection between users and the rights they are given. Instead of assigning rights to users directly, rights are assigned to roles, and then users are assigned one or more roles. Permission checks are made simpler because now we only need to consult one of a far smaller collection of roles, and user management is easier because users are simply assigned roles rather than access rights. If new access rights are introduced in a later phase or revision, either these rights can be assigned to existing roles (thus meaning no change to the user base) or new roles can be introduced if necessary.

The J2EE servlet and EJB containers contain sections on security, specifically on authorization and access controls; once the user has authenticated into the system (either by going through the built-in j_security_check action in servlet containers or by authenticating via JNDI properties during the EJB bean Home lookup), the container can consult a vendor-specific user database to determine the user's configured roles. From here, programmers can allow/restrict access to URL resources (servlets or JSPs) or bean methods (on EJBs) by two methods: (1) by listing those restrictions explicitly in the deployment descriptors of the J2EE component (Web application or enterprise archive), or (2) by using the programmatic APIs provided by both environments, most notably the isCaller InRole method. Using the deployment descriptors allows for coarse-grained security controls—for example, doing so on a JSP means users will be granted or denied access to the entire page—whereas using the programmatic APIs allows for a more fine-grained approach.

However, despite the ease and flexibility that the servlet and EJB specifications offer, they miss one important aspect of authorization control: controlling access to resources outside the J2EE sphere of influence. For example, opening a file on the underlying filesystem normally requires an appropriate permission at the Java platform security level. However, because the J2EE security system is entirely orthogonal to Java's Security Manager APIs, there's no way to grant access to the filesystem based on the end-user's role without explicitly putting a check in front of the code that wants to open the file:






// EJB bean

EJBContext context = ...;

if (context.isCallerInRole("admin"))

{

  // Do whatever admin-only processing is necessary

  //

}



// servlet

HttpServletRequest request = ...;

if (request.isUserInRole("admin"))

{

  FileOutputStream fos = new FileOutputStream(. . .);



  // Do whatever admin-only processing is necessary

  //

}


In many respects, this is a bad situation on multiple levels. First, if an attacker can somehow find a flaw in your code, the underlying platform security model (which is built specifically to prevent such kinds of attacks and thus does a complete stack walk on a security check) won't be able to help stop the attack. More importantly, however, it's too easy for a developer on your team to forget to put the J2EE security check in front of the code to open the file, particularly if it's 3 A.M. and you're supposed to ship the application the next day. Or, worse yet, "I know we should put this security code in here, but it's working now, so let's ship it and fix it later—it's not like an attacker's going to know the flaw's here, right?"

What we'd really like to see is an integration of the J2EE role-based authorization security model with the underlying platform security model, so that we can bring the benefits of both security models to bear on our applications' needs. Unfortunately, not only does the J2EE role-based authorization model not hook on to the platform security model, but up through JDK 1.3, the platform security model had no concept of user-based authorization rights.

Between the JDK 1.3 and 1.4 releases, Sun introduced a new API, the Java JAAS, which corrects this egregious oversight. In a sentence, JAAS extends the Java standard security model to include authorization based on identity, as well as the code's source. In short, the standard platform security model now understands user-based authorization, which means that integrating role-based authorization into the platform security model is now possible. In case you're wondering whether it's worth reading the rest of this item ("Bah, J2EE security is enough for me"), a new JSR, the Java Authorization Contract for Containers (JSR 115), effectively integrates J2EE security with the underlying JAAS and Java platform security model, so you're going to be here sooner or later anyway. Fortunately, using JAAS is not a difficult thing to do, once you've taken care of some basic overhead. (Other sources, such as Inside Java 2 Platform Security [Gong], cover JAAS in detail, so in this discussion I'm just going to walk through some of the high-level steps necessary to use it.)

A JAAS security setup has two elements. First, because JAAS supports a pluggable authentication model, meaning it can support multiple kinds of authentication and authorization systems simultaneously, JAAS needs to know which pluggable modules it will be working with. This is called the JAAS configuration, and while it's possible to establish this programmatically (see the javax.security.auth.login.Configuration class), it's far more common to use the default Configuration implementation that reads a text file identified by the JVM system property java.security.auth.logon.config. The contents of this text file look something like the following:






MySystem

{

  com.tagish.auth.RdbmsLoginModule Required

    driver="org.gjt.mm.mysql.Driver"

    url="jdbc:mysql://localhost/userdb?user=root";

}


(In this particular example, we're using an open-source LoginModule provided at http://free.tagish.net/jaas that will use a relational database for its user database. It's possible to build your own LoginModule implementations, but that's beyond the scope of this item; either see Java Security [Oaks] for details or do a Google search for some of the Internet articles and papers that describe how to do so in greater detail.)

The second part of the setup is the actual assignment of permissions to user roles, which we'll cover later.

Having established the configuration (remember to pass the system property on the container startup command line), upon user entry into the system, we authenticate the user by first creating a LoginContext referencing the configuration block we defined previously:






// In the servlet acting as the recipient of the

// login.jsp page

//

final String username = request.getParameter("uid");

final String pwd = request.getParameter("pwd");

LoginContext loginCtx =

  new LoginContext("MySystem", new CallbackHandler()

    {

      public void handle(Callback[] callbacks)

        throws IOException,

               UnsupportedCallbackException

      {

        for (int i=0; i<callbacks.length; i++)

        {

          Callback cb = callbacks[i];

          if (cb instanceof NameCallback)

            ((NameCallback)cb).setName(username);

          else if (cb instanceof PasswordCallback)

            ((PasswordCallback)cb).setPassword(pwd);

          else

            throw new

              UnsupportedCallbackException(cb);

        }

      }

    });

loginCtx.login();

Subject userSubject = loginCtx.getSubject();

HttpSession session = request.getSession(true);

session.setAttribute("userSubject", userSubject);



// userSubject now represents the user


A couple of things are happening here at once. The goal is to call login on the LoginContext, but part of that means we have to pass the authentication credentials to the various modules defined as part of this Login Context (which were identified in the configuration file defined earlier). Because JAAS doesn't necessarily know what's needed to do a login ahead of time, it uses a callback approach to gather that information. JAAS doesn't want to make any assumptions about the environment it's executing within; normally, in an interactive application, the callback would pop up some kind of prompt to the user to gather the necessary information, but in a Web application this is obviously not going to work. So, instead, JAAS requires an object that implements the CallbackHandler interface to be passed into the LoginContext constructor, and then JAAS makes calls on that object to gather its necessary information. In the case of the code above, we provide an anonymous inner-class implementation that simply hands back the strings gathered by the JSP form that fronted this servlet and throw an exception in the event the various authentication systems need something more than username and password.

Once the LoginContext has been successfully constructed, we simply call login to tell JAAS to authenticate this user against the set of login modules specified in that configuration file. Assuming it is successful (it throws a LoginException if it isn't), it returns a Subject instance, which essentially represents this user. Cache this thing off—it's a Serializable object for a reason—by storing it into either HttpSession or whatever mechanism you're using for per-user data. (This is one case where we have to have some kind of per-user state, so we have to ignore the arguments made in Item 39 and store it in an HttpSession or equivalent.) Alternatively, cache off the LoginContext itself, since you can always obtain the Subject from the LoginContext via the getSubject call, and the LoginContext has another method, logout, to close the resources associated with the authenticated user. (As a matter of fact, the LoginContext implementation that ships with the Sun JDK has no finalizer, so unless you call logout, any external resources allocated to the Subject as part of login will never be cleaned up; do some profile testing to make sure releasing the last reference to the LoginContext doesn't create a resource leak.)

Now, having put the Subject into HttpSession, on each subsequent JSP and/or servlet access by the user, we can now have the servlet impersonate that user for the duration of the HTTP request processing:






// Inside the controller servlet or JSP

//

Subject userSubject =

    (Subject)session.getAttribute("userSubject");

Subject.doAsPrivileged(userSubject, new PrivilegedAction()

  {

    public Object run()

    {

      // Do the usual servlet processing logic;

      // inside here, it will be done under

      // userSubject's rights

      //

    }

  }, null);



// Out here, code will execute as the container

//


The Subject.doAsPrivileged method call essentially tells the executing thread to change its security policy for a bit to mimic that of the user (whose set of available permissions we'll see how to configure in just a moment), so that any calls within the body of the run method, which is called by Subject.doAsPrivileged once the impersonation is established, are done under the Java platform security semantics. This means that if the servlet attempts to open a file on the filesystem, and the user (or, perhaps more accurately, any roles the user is a part of) doesn't have that permission established, a standard Java SecurityException will be thrown. (Within a JSP page, rather than using the plain Java code just listed, it's probably a better idea to write—or download, if one is available, which wasn't the case as of this writing—a JSP custom tag handler to do the impersonation check.)

To configure the authorization policy for a given user role, go to the standard Java security policy file (found at $JRE/lib/security/java.policy) and build some authorization blocks in the normal Java platform security fashion. Note that you can do this directly inside this JRE-wide file because you've established an individual JRE just for this container, as recommended by Item 69; if not, you'll need to create a custom policy file and point the servlet container to it via another system property. The policy file looks something like the following:






// Java.policy file

//



// Permissions granted to all users

//

grant Principal * *

{

  permission java.security.BasicPermission

    "menuActions","logout";

  // Other permissions here

}



// Permissions granted to users in the "users" role

//

grant Principal com.tagish.auth.TypedPrincipal "user"

{

  permission java.security.BasicPermission "menuActions", "save";

  // Other permissions here

}



// Permissions granted to users in the "admin" role

//

grant Principal com.tagish.auth.TypedPrincipal "admin"

{

  permission java.security.BasicPermission "menuActions",

    "delete";

  // Other permissions here

}



// And so on


(Again, the Principal classes are from the open-source library mentioned earlier.) Now, having granted each role a block of permissions (which are represented by Java Permission objects and/or subclasses[4]), we can write code in JSPs and servlets to test whether users have the appropriate permission without having to worry about who the user is, what his or her assigned roles are, or anything beyond the actual Permission object required to execute:

[4] See Inside Java 2 Platform Security [Gong] or Java Security [Oaks] on how to create Permission subclasses; it's pretty easy to do.






<%!-- In a JSP page --%>

Menu options:

<% try {

    AccessController.checkPermission(

      new BasicPermission("menuActions","logout");

  %>

<a href="logout.jsp">Logout</a>

<% } catch (SecurityException secEx)

   { /* We ignore this; they don't have access,

        which is OK */ } %>



<%!-- The above is a bit tedious, so we would

      probably replace it with a JSP custom tag

      handler that would look like the following:

   --%>

<taglibrary:checkPermission

     type="java.security.BasicPermission"

     name="menuOptions" actions="save">

    <a href="save.jsp">Save</a>

</taglibrary:checkPermission>



<taglibrary:checkPermission

     type="java.security.BasicPermission"

     name="menuOptions" actions="delete">

    <a href="delete.jsp">Delete</a>

</taglibrary:checkPermission>



<%!-- And so on --%>


Again, more information on JAAS is available from the Web and the Java security books mentioned earlier [Gong; Oaks]. As a last comment about JAAS, however, note that the Win32 JDK and Solaris JDK from Sun both include LoginModule objects that use the underlying operating system security database to do authentication; never use these for your Web applications. On top of requiring system administrators to do operating system user administration every time a new user wants to use the Web application, using LoginModule transmits users' operating system passwords across the HTTP link to the server, so every possible link between the user and the servlet container needs to be locked down in order to avoid Eve (the eavesdropper) sniffing out operating system passwords. In addition, if an attacker manages to guess the password to one system (either the operating system or the Web application), he or she already has a leg up on attacking the other—this is not a defense-in-depth approach (see Item 60).