ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Java vs. .NET Security, Part 4
User Authentication and Authorization

by Denis Piliptchouk
02/25/2004

This is the fourth and final article in this series comparing Java and .NET security. It discusses implementations of user authentication and authorization on those platforms. Previous articles of this series covered configuration and code containment in Part 1, cryptography support and the mechanisms of communication protection in Part 2, and code protection and Code Access Security (CAS) in Part 3.

When authentication comes into play, the system should already have a strong foundation, defined by the features discussed in previous articles. Authentication adds to that bag an ability to determine whether the user is the person he claims to be. Results of the authentication process are usually passed on to the authorization step.

The issue of user authorization (a.k.a. role-based security) comes after solidifying the platform's base. At that point, in any more or less advanced system the administrator is left to be the judge, determining who is allowed to do what. This is traditionally done in two ways: using ACL to protect a particular resource (this is known as Discretionary Access Control, or DAC), or checking a user's group (or role) membership and allowing/denying him an operation based on the results of this check (a variation of Mandatory Access Control, or MAC).

User Authentication: General

The process of authentication starts right after identification by collecting caller credentials, confirming the identity claim, and securely communicating them to the server. Those credentials (possibly several types of them, so it's called multi-factor authentication) are matched against the registered account information and a positive or negative answer is returned regarding the claimed identity.

To do this work, application developers can either utilize standard platform facilities, as described below, or roll out some custom authentication solution (for instance, biometric readers), which is outside of the scope of this article. This article also does not cover RMI and remoting authentication, since their status was already discussed in Part 2.

Related Reading

Programming .NET Security
By Adam Freeman, Allen Jones

.NET includes a web solution for the server side: ASP.NET, which is coupled with IIS for processing HTTP requests. It is also possible to attach it to a different web server, if an appropriate server extension is supplied. The IIS extension is called aspnet_isapi.dll, and handles all requests directed to ASP.NET (the suffixes ASPX, ASMX, etc). ASP.NET itself, however, runs separately from IIS, in the aspnet_wp.exe process, so process isolation settings in IIS do not matter much. All managed code is executed in the worker process, and requests are forwarded there from the aspnet_isapi.dll extension through a named pipe.

Based on its configuration, IIS can either authenticate the requestor against a Windows account, using one of its standard methods (NTLM, Kerberos, Basic, Digest, Certificates) before forwarding the request, or forward the unauthenticated call to the ASP.NET handler. It is important to remember that security settings of IIS and ASP.NET are unrelated, although the latter uses IIS services for particular kinds of authentication.

ASP.NET handles authentication via so-called Authentication Modules, one per each authentication type that APS.NET supports, which reside in the System.Web.Security namespace. They all provide an OnAuthenticate event handler, which can be used to create a custom authentication/authorization schema by using different user account mapping and attaching new custom principals to the thread context.

The Java platform defines two solutions for user authentication to the servers: JAAS and servlets. Although EJB does not have its own authentication facilities, and its 2.1 specification does not require any particular authentication mechanism from vendors, it does mention the requirements for propagating the Principal object (see the Identities section), created in a server-specific manner.

Java servlets is the Java platform's HTTP-oriented server layer, which performs HTTP processing functions similar to those of the ASP.NET layer. Correspondingly, the servlet's security model is intended specifically to handle the requirements of web applications.

JAAS may be used to add authentication and authorization to any Java-based application (executable, bean, applet, etc). It defines an API-based, configurable generic authentication mechanism, independent of the underlying methods. The power of this approach lies in the clear separation of application and authentication code, allowing transparent replacement or alteration of authentication mechanisms.

User Authentication: Identities

In both systems, a principal and his identity (or identities) are established as a result of the authentication process, which serve to represent the user in the application during his further requests.

A user and his roles are represented in .NET via objects, implementing the IIdentity and IPrincipal interfaces, attached to the current thread context. IIdentity provides access to name and authentication type information, and IPrincipal provides access to the contained user identity (one-to-one relationship) and role membership information. .NET provides two sets of implementations of those interfaces -- WindowsPrincipal with WindowsIdentity, and GenericPrincipal with GenericIdentity. If the user does represent a Windows authorized account, he may use a WindowsIdentity, and this object represents a Windows security token, with role membership and authorization type derived from the Windows token. Generic versions of interfaces are used to implement any additional type of principal, unrelated to Windows accounts.

WindowsPrincipal principal = 
      (WindowsPrincipal)Thread.CurrentPrincipal;
WindowsIdentity identity = 
      (WindowsIdentity)principal.Identity;

There is no required relationship between the identities used by the CLR and the current Windows process token, because the CLR has a separate security context from that of Windows. In fact, the CLR thread might not have an associated identity at all (or, rather, an empty one), while Windows threads always have one. In order for the CLR thread to take on the Windows thread's WindowsIdentity (to synchronize, using .NET jargon), it has to be configured to use WindowsAuthenticationModule. Otherwise, CLR and process threads will have two different identities.

As opposed to .NET's hierarchy, Java uses the word Principal, and the corresponding interface, java.security.Principal, to represent a user's identity. This user object carries only username information in it, not roles or any additional attributes about the logged-on user. This design reflects the focus on Code Access Security prevailing in J2SE, since user-access checks were not initially the main point of concern for Java designers. As for the identity synchronization with the OS thread, the J2EE specifications merely state that for single sign-on capabilities, a compliant J2EE product must be able to relate those identities.

Clearly, having only a username is not sufficient for any kind of serious application, so JAAS augmented it with additional information. JAAS groups multiple Principal objects, representing the same user, into a single Subject, which also holds the user's credentials, such as password, certificate, or any kind of user-related information.

public final class Subject {
    public Set getPublicCredentials(); //not security-checked
    public Set getPrivateCredentials(); // security-checked
    public Set getPrincipals();
    ...
}

User credentials, obviously, have to be stored somewhere, for matching them later during the authentication process. Both platforms support multiple storage formats for user accounts: OS, disk-based files, database, and directory services. Necessary care must be taken to configure these storage areas properly, using file protection, secure hosts, communication encryption, etc.

ASP.NET applications have several options for storing user credentials:

Neither Java nor J2EE specifications define any specific storage means for user accounts. The applications are capable of using directory services via the JNDI mechanism, as well as other custom or vendor-provided solutions. Additionally, most commercial J2EE application servers provide some kind of mapping between the underlying OS' accounts and J2EE users and groups.

When a user logs in, a new session is created on the server and associated with that user. Servers typically terminate user sessions after some period of inactivity, as configured or set in the code.

ASP.NET applications map user requests to the Session objects, with their timeouts (in minutes) determined by sessionState tags in the web.config application file or through a global setting in machine.config.

<sessionState timeout="20"/>

To track user sessions, Java servlet engines use the HttpSession object. The sessions can be managed automatically and/or manually, providing expiration time to prevent session hijacking. The servlet container determines the default timeout for servlets sessions, which can be retrieved by calling HttpSession.getMaxInactiveInterval, and changed by calling HttpSession.setMaxInactiveInterval. Specifying "-1" as the timeout interval means that the session never expires. An application can also override the default timeout by setting the desired value (in minutes) in its web.xml file:

<web-app>
  <session-config>
    <session-timeout>20</session-timeout>
  </session-config>
</web-app>

In EJB servers, Principals are associated with callers' requests in server-specific ways. J2EE specifications require that for all EJBs in a call chain within the same application the same identity must be returned for all calls to EJBContext.getCallerPrincipal, which should be the same identity as in HttpServletRequest.getUserPrincipal if that is not null. Whereas servlet specifications do allow returning a null Principal, EJB specifications explicitly state that a non-null object should be returned at any time, even for representing an unauthenticated user.

EJB specification does not dictate any programmatic ways of propagating principals in the case of calling multiple beans or even multiple servers. Some EJB servers implement a principal delegation mechanism akin to Java's doPrivileged privileged code execution. If desired, the application assemblers, via the deployment descriptor, may affect the choice of identities that execute their beans. There is <security-identity> element for that, which has two possible values:

User Authentication: Web Mechanisms

There is a standard set of web-based authentication methods that may reasonably be expected by application developers on a particular platform. Generally supported authentication mechanisms include HTTP authentication (basic and digest), forms/cookies authentication, and certificate authentication. The latter is usually coupled with mutual SSL/TLS authentication -- this is the standard way of implementing it.

Forms authentication is normally performed with the help of a cookies mechanism. Two types of cookies are in use: temporary and persistent. The former last only during the current browser session, and the latter are stored at the client's computer. Both types have an expiration time, to prevent identity theft, but persistent cookies are typically stored on the client's computer and remain valid for many days, so they pose a greater security threat.

Certificate authentication is significantly more secure, as it allows mutual authentication, so the client can be assured that it connects to the proper server. Configuring it, however, is more problematic, because it requires installing an X509 certificate on the client side.

ASP.NET heavily relies on IIS for its authentication needs. In fact, it uses ISS to implement all of the above authentication modes, except for forms, and merely consumes the results of the authentication process performed by IIS. Therefore, forms authentication is the only one that is going to be further discussed here for ASP.NET.

Authentication is handled by FormsAuthenticationModule, which handles all traffic received from IIS via the extensions mechanism, as shown in Figure 1. It passes all authenticated requests through, while forwarding all unauthenticated ones to the specified logon page. The authentication, once performed, is sustained via the cookie mechanism, which can be made to expire after a timeout to prevent user identity theft. Alternatively, another form-authentication scheme can be developed; for example, using hidden fields to store credentials in the form, and taking full control of authentication process by providing a FormsAuthentication_OnAuthenticate handler event in the Global.asax file. It is possible (although not trivial) to create a completely cookieless authentication schema using this method.

Figure 1
Figure 1. ASP.NET Forms Authentication

ASP.NET provides a helper class, FormsAuthentication, to help with common authentication tasks: authenticating username and password; issuing, encrypting, and decrypting tickets; redirecting a user request to the originally requested page after successful authentication; and signing out. An authenticated user is identified by the presence of an authentication cookie (either temporary or persistent), which is usually implemented by the FormsAuthenticationTicket class. However, a custom cookie may be set in the code -- this allows better control over its expiration property, as well as over the cookie's content.

string data = "Application data";

HttpCookie cookie = new HttpCookie(

    FormsAuthentication.FormsCookieName, data);

//expires in 10 minutes

cookie.Expires = DateTime.Now + new TimeSpan(0,0,10,0);

Response.Cookies.Add(cookie);

Custom content in a cookie may be protected by encrypting, via the FormsAuthentication.Encrypt call, with a user-provided or auto-generated 3DES key, which is then stored at the server's Local Security Authority (LSA). HMAC validation with a specified algorithm may also be requested.

<machineKey validationKey="AutoGenerate" 

            decryptionKey="AutoGenerate" 

            validation="SHA1"/>

The associated authentication cookie's name and expiration timeout may be configured in the top-level application's web.config file. Cookies are renewed upon the next request after half of their expiration time has passed, thus keeping them from expiring. However, as was explained earlier, the server-side session associated with the user's identity expires after a certain period of inactivity, as determined by the timeout setting in the sessionState element. So, even if a request has a valid cookie, if the corresponding session has expired, the user will still be prompted to re-authenticate. Calling FormsAuthentication.SignOut will terminate any session association of the current user and remove cookies from the browser's cache.

<authentication mode="Forms">
  <forms forms="DemoApp" 
         Loginurl="https://www.DemoApp.com/login.aspx"
         Name=".DEMOAPPCOOKIE" 
         protection="All" Timeout="30" Path="/">
  </forms>
</authentication>

The authentication sequence works in the following way: after a request comes in, it is forwarded to the OnAuthenticate handler, if present. Here, any additional information may be extracted from the custom cookie or URL, and additional roles assigned. If a user identity is associated with the request after the finishing handler's execution, no further actions are taken. Otherwise, the request is checked by name for the presence of authentication cookie. If such a cookie is found, it is used to construct the appropriate Principal and associate it with the current request; otherwise, the request is forwarded to the logon page.

The Java servlet specification requires support for the basic HTTP authorization mechanism, and encourages (but does not require!) support for digest authentication, because it is a fairly rare mechanism. Additionally, two other forms of authentication are required for J2EE compatibility: form-based and mutual certificate (HTTPS client). Basic, digest, and certificate authentication are carried out transparently, between the web server and the connecting client, and do not require writing additional code.

<auth-method>BASIC|DIGEST|FORM|CLIENT-CERT
    </auth-method>

The form-based authentication schema is probably the most common option in use today. The specifications require the presence of the following names on the logon form: a j_security_check action, and the fields j_username and j_password. These indicate to the servlet engine that this is the logon information to process.

<form method="POST" action="j_security_check">
  <input type="text" name="j_username">
  <input type="password" name="j_password">
</form>

A container creates a persistent or temporary cookie named JSESSIONID for the user request, sets its expiration policy via a call to Cookie.setMaxAge before adding the cookie to the current session, and then keeps sending it back to the client with each response. The client returns it with each request, which allows mapping the connectionless requests to the user's session. Alternatively, a container may use a technique called URL Rewriting to support those clients that do not accept cookies:

http://www.SomeHost.com/servlets/index.html;jsessionid=0124343

Note that using a GET operation in form-based login is a bad idea -- it puts the entire request, with all of its fields, into the URL, thus making it available when browsing the server log. For instance:

http://www.DemoServer.com/login?j_username=MyName&j_password=MyPassword

Using the POST operation, the URL will be as shown below, so the user information will not show up in the server's log:

http://www.DemoServer.com/login

When a request comes for one of the protected resources, the engine checks the user's authentication, and if he is has not been authenticated yet, forwards him to the login page associated with the resource. The servlet engine is then responsible for redirecting the user back to the originally requested resource (or error page, in case of a failure).

<web-app>
  <login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
      <form-error-page>/Error.html
      </form-error-page>
      <form-login-page>/SignOn.html
      </form-login-page>
    </form-login-config>
</login-config>
</web-app>

Certificate authentication is configured declaratively on the server side, but support for mutual HTTPS communication is required on both sides. The client's request should contain a certificate that can be mapped to a server's defined principal, which is going to be associated with this and further requests. Note that HTTPS, as opposed to HTTP, is a stateful protocol, and cookies are not needed to maintain session association.

<web-app>
  <login-config>
    <auth-method>CLIENT-CERT</auth-method>
  </login-config>
  <user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  </user-data-constraint>
</web-app>

From inside of a servlet, client certificate information can be retrieved by accessing the getAttribute method of the javax.servlet.http.HttpServletRequest class, requesting the following attribute:

X509Certificate cert = 
  (X509Certificate)request.getAttribute
     ("javax.servlet.request.X509Certificate");

In the case of a HTTPS connection, the servlet engine sets this attribute, as required by the servlet specifications, before invoking the target servlet. Using attributes of the returned X509Certificate object, the servlet can perform any additional programmatic authentication of the caller.

Note: .NET delegates all types of user authentication (except for forms) to IIS, and barely consumes the results. J2EE requires support for all standard authentication mechanisms from the compliant servers.

User Authentication: Other Mechanisms

Besides the standard web-based mechanisms, both platforms provide other means for authentication.

In .NET, Windows and Passport authentication are incorporated as separate entities via the corresponding modules. They both are used together with IIS, and require very little configuration in the application's configuration file.

WindowsAuthenticationModule relies on IIS to authenticate the caller, as shown in Figure 2, and attaches WindowsPrincipal object to the application context. This is the default provider for ASP.NET, and the easiest to use in pure Microsoft network environment, as it requires no additional application code.

<authentication mode="Windows">
</authentication>

Figure 2
Figure 2. ASP.NET Windows Authentication

PassportAuthenticationModule is a wrapper around the Passport SDK that creates a PassportIdentity object using Passport service and profile, as shown in Figure 3. This identity object provides access to the Passport profile and allows the encrypting/decrypting of authentication tickets. Most of the authentication details are handled by the ASP.NET framework; the developer can control the process by overloading the OnAuthentication handler.

<authentication mode="Passport">

</authentication>

Figure 3
Figure 3. ASP.NET Passport Authentication

In Java, JAAS serves as a general abstraction for providing authentication services to applications. JAAS relies on Pluggable Authentication Modules (PAM) in its operation to provide a flexible authentication framework. Administrators can add various implementations to the environment and modify its behavior and authentication method. The default PAM is username/password based; however, it is possible to use alternative schemas. In JDK 1.4, Sun provides implementations for the following login schemas via its LoginModule implementations: UnixLoginModule, NTLoginModule, JNDILoginModule, KeyStoreLoginModule, and Krb5LoginModule. Additionally, there exist implementations of SmartCard login modules by independent vendors; for instance, GemPlus.

A sample Java application, which demonstrates some of the discussed JAAS authentication techniques, can be found here.

In this section, only the authentication part of JAAS will be reviewed. Its operation is controlled by the LoginContext, which uses the Configuration class to retrieve the specified LoginModules. Those modules retrieve credentials with the help of provided Callbacks, although it is possible to use other means as well. There might be several LoginModules configured, and during the login process all of them are queried in turn, which is shown in Figure 4.

Figure 4
Figure 4. JAAS Login

The relationship among LoginModules is determined by the strategy configuration settings, which tells the system how to treat login failures in individual modules:

JAAS introduces a couple of new permissions, javax.security.auth.PrivateCredentialPermission and javax.security.auth.AuthPermission, to guard access to the Subject, LoginContext, and Configuration classes. The code that works with JAAS classes will need to have them (especially AuthPermission) granted in java.policy. See the online Javadoc for details about their usage.

The following classes are used as part of JAAS authentication process:

Different user identities and roles are represented via Principals, added to the resulting Subject by the configured LoginModules during the commit phase, and removed during logout. Their credentials may also be added to that object, as well as any additional identification information.

public abstract class DemoLoginModule implements LoginModule {
  protected Subject m_subject;
  protected ArrayList m_principals = null;
  public boolean commit() throws LoginException {
    // Login succeeded, 
    // add demo Principals to the Subject.
    if (!(m_subject.getPrincipals().containsAll(
                                m_principals))) {
        m_subject.getPrincipals().addAll(
                                   m_principals);
    }		
    return ret;
  }
  public boolean logout() throws LoginException {
    // Need to remove our 
    // principals from the Subject.
    if (null != m_principals) {
      m_subject.getPrincipals().removeAll(
                                   m_principals);
      m_principals = null;
    }
    return true;
  }
}

The interaction of the various JAAS classes during initialization process is shown in Figure 5 below.

Figure 5
Figure 5. JAAS Initialization

Note: JAAS provides a flexible and versatile mechanism for adding authentication to any type of Java application.

User Authentication: Impersonation

In addition to the direct login, an application can impersonate the logged-in user while performing sensitive operations locally, or delegate his identity when communicating with remote servers. This way, the execution will happen in the context of the logged-in user, with privileges granted to the user's identity and not those of the application.

.NET's impersonation has a distinctive feature of being in a close relationship with the Windows process' identity. Turning on impersonation in an ASP.NET configuration will result in ASP.NET's Windows execution thread borrowing the security token of the calling IIS process. So, as far as Windows security system is concerned, ASP.NET Windows thread's identity will be the same as that of the IIS thread, although CLR identity may be completely different, as has been explained before.

Sample ASP.NET configurations are listed below.

When knowing account's credentials, it is possible to impersonate not only the currently logged-in user, but also an arbitrary user. WindowsIdentity has an overloaded constructor that accepts a Windows account token. That token can be retrieved by making an unmanaged call to the Windows function LogonUser (permissions permitting, of course).

// Impersonating a logged-in Windows user
<%
WindowsIdentity id = 
        new WindowsIdentity(userTokenHandle);
WindowsImpersonationContext ctxt = 
                            id.Impersonate();
...
ctxt.Undo();
%>

An important point to remember about Windows impersonation is that it was designed for use with trusted code only -- any unmanaged DLL down the call chain can call RevertToSelf and start using IIS process identity, which will most likely be System. Although managed code is a subject to CAS permission checks, which generally deny the corresponding security permissions to most applications, it does not apply to the locally installed code, which has FullTrust under the default policy.

Among .NET's authentication options, delegation is currently supported only by the Kerberos protocol, which is used only with WindowsIdentity for now. In order to use a Windows identity for delegation, the impersonated user's Windows account should be granted the right "Act as a part of OS" by the administrator; this is not given on a general basis.

Java uses JAAS to implement impersonation on the application level. JAAS' Subject class allows executing a particular sensitive operation (marked so by implementing the java.security.PrivilegedAction interface) as another user's identity.

public final class Subject { 
  ... 
  // associate the subject with the current 
  // AccessControlContext and 
  // execute the Privileged action 
  public static Object doAs(Subject s,
       java.security.PrivilegedAction action);
}

When this operation is run, the doAs method associates the impersonated subject with the current AccessControlContext, and then executes the action. At the end of the doAs call, the subject is removed from the AccessControlContext:

//class representing a protected operation
class ProtectedOperation 
                 implements PrivilegedAction {
  //do something veeeery sensitive here...
  public Object run();
}

public class ImpersonationExample {
  public static void main(String args[]) {
    ...
    //carry out the authentication process
    Subject subject = loginContext.getSubject();
    //run as the impersonated user
    Subject.doAs(subject, 
                 new ProtectedOperation());
  }
}

In the default JDK implementation, Java impersonation is limited to application level only -- specifications do not define any relationship to user accounts on the underlying OS. Specific vendor implementations can implement functionality that maps the logged-in user to the OS domain names. For instance, WebLogic, if configured, can use NT PAM to authenticate users against Windows account names.

However, GSS-API, in combination with JAAS, can handle both impersonation and delegation, as shown in Figure 6 below.

Figure 6
Figure 6. GSS/JAAS Authentication

It is possible to configure the Kerberos provider to use an existing credentials cache so that the login happens completely transparently.

// client JAAS configuration for GSS-API
com.sun.security.jgss.initiate {
  com.sun.security.auth.module.Krb5LoginModule 
                                      required;
};

// server JAAS configuration for GSS-API
com.sun.security.jgss.accept {
  com.sun.security.auth.module.Krb5LoginModule 
                                      required
                  useKeyTab=true storeKey=true 
                          principal="nfs/host";
};

// default configuration for GSS-API 
// if the above is not present
other {
...
}

Note: .NET provides good support for impersonation on Windows-only networks, but delegation across the Internet is not possible. Java can do application-level impersonation and is capable of supporting delegation across the Internet.

User Access Security: Basic

Once a distinguished principal has been identified as a result of the authentication step and attached to the call context (usually associated with threads), it can be used in determining resource access rights. In role-based systems, application code may operate not only with specific principals, but also with their abstract roles, which results in a more flexible system configuration. So after establishing a principal, the server goes through an additional step of mapping it to the possible application roles.

Each executing .NET thread has an associated CallContext, which carries around the Principal and his Identity -- they are either copied from the creating thread, or created anew by the CLR when code tries to access them for the first time.

WindowsPrincipal principal = 
  (WindowsPrincipal) Thread.CurrentPrincipal;
WindowsIdentity identity = 
  WindowsIdentity.GetCurrent();

A configurable policy governs the type of principal created by default: NoPrincipal, UnauthenticatedPrincipal, or WindowsPrincipal. An application thatis granted the appropriate SecurityPermission can set this policy imperatively:

AppDomain.CurrentDomain.SetPrincipalPolicy(
  PrincipalPolicy.WindowsPrincipal);

An application that possesses a proper SecurityPermission to control the principal can replace the current thread's principal. However, this permission is not required for normal role-based checks:

GenericIdentity id = new GenericIdentity("user");
String[] roles = {"Manager","User"};
GenericPrincipal pr = new GenericPrincipal(
                                      id,roles);
Thread.CurrentPrincipal = pr;

To provide more consistent security architecture, .NET incorporates role-based security into code-access hierarchy by providing a PrincipalPermission, available for both declarative and imperative checks. Checks can be performed by name, role, or a combination of both.

[PrincipalPermissionAttribute(
    SecurityAction.Demand, Role = "PowerUser")]

Optionally, principal permission objects can be combined in code (but not declaratively!) to support checking several identity/roles at once:

PrincipalPermission perm1 = 
   new PrincipalPermission("John","Admin");
PrincipalPermission perm2 = 
   new PrincipalPermission("PowerUser");
(perm2.Union(perm1)).Demand();

Finally, ordinary checks for user names and role can be performed in code by directly accessing the IPrincipal object:

Principal principal = Thread.CurrentPrincipal;
if (principal.IsInRole("Admin"))
{
  //do something for Admin
} else if (principal.Identity.Name == "John")
{
  //do something for John
}

An application demonstrating the user access checks in .NET can be downloaded from here.

It is important to realize that .NET policy cannot extend the final permission set granted to the assembly, based on user's identity. In other words, if an assembly A is granted (as a result of policy evaluation) permission set PA, the same will be granted happen for any user executing this assembly. This set can be further restricted based on the results of role and user checks. This is in contrast to the way most modern operating systems, including Windows, work -- a user is granted certain additional privileges based on his identity or group membership.

In Java, JAAS grants permissions based on user identity, as defined by name, as opposed to the pure policy-based model, which grants the permissions based on the code's origin. Declarative security is set through the java.policy file -- JAAS adds Principal entries to the Java policy. As an important difference from the .NET model, JAAS' principal-based model can extend the permission set granted to a module. In the example below, the code, signed by "MyPublisher", is granted write permissions to "C:\" only if it is executed by "user":

grant Signedby "MyPublisher" {
	permission java.io.FilePermission "c:\","read";
}

//an example of grant by username
grant Principal com.comp.PrincipalClass "user" {
	permission java.io.FilePermission "c:\","write";
}

Principal-based access policy enforcement is performed using PrivilegedAction and impersonation. As explained in Part 3, running this class effectively asserts all privileges granted to the code, including those based on the current Principal. Technically, after doAs has been called with an impersonated Subject, java.lang.SecurityManager updates the current AccessControlContext from the policy file, adding permissions for the impersonated user. An internal JAAS implementation of java.security.DomainCombiner is responsible for instructing the installed SecurityManager to query JAAS policy and update the AccessControlContext. In the server environment, which concurrently handles multiple calls, it is important to use doAsPrivileged and pass it null AccessControlContex to force policy re-evaluation by the Combiner and to create a new context customized for the user, instead of borrowing the server's existing one. At the security checkpoints during the execution, the total granted permission set now includes code-based and Principal-based application permissions.

This application demonstrates the effects of dynamic policy evaluation in JAAS.

There is no notion of roles in the JAAS hierarchy; everything is determined by usernames. Although it is not very convenient, roles and groups may be treated as named principals, and access control may be imposed on them in the same way. Moreover, since a Subject may contain any number of Principals, objects representing role(s) can be added to its Principal collection. Later the Subject's roles may be retrieved by requesting principals of only a particular class, which denotes a particular role. To build application name-based role hierarchies, JAAS defines the com.sun.security.auth.PrincipalComparator interface, which may be implemented by the Principal classes specified in the policy's grant entries. The PrincipalComparator.implies method should return true when the specified Subject is in a particular role:

// an example of role-based entry
grant Principal com.MySite.AppRole "PowerUser" {
  permission java.io.FilePermission "c:\","read";
}

// this class is used for building role hierarchy
public class AppRole implements 
                      PrincipalComparator {
  // the role this object represents
  public AppRole(String role) {...}

  //this method checks the Subject 
  //for being in the role
  public boolean implies(Subject currSubject) {
    ...
  }
}

Subjects are assigned by JAAS to the current thread's execution context, and are available for examining directly from the code -- therefore, programmatic security checks can also be based on principal names, as obtained from the current execution context.

AccessControlContext ctxt = 
                     AccessController.getContext();
Subject subj = Subject.getContext(ctxt);
if (subj == null) {
  //no authenticated user
} else {
  Set principalsSet = subj.getPrincipals();
  Iterator iter = principalsSet.iterator();
  while(iter.hasNext()) {
    MyPrincipalClass princ = 
      (MyPrincipalClass)iter.next();
    if (princ.getName().equals("MyUser")) {
      // have an authenticated user
    }
  }
}

Note: .NET has a very convenient, permission-based user access system. However, it can only restrict the total permission set for an assembly, never extend it. JAAS makes use of dynamic policies in Java to extend a granted permission set with user-specific permissions.

User Access Security: Extended

In addition to the basic facilities for user access checks, extension packages on both platforms define their own mechanisms.

ASP.NET provides security checks, which work on the top of regular CLR security facilities:

Java servlets and JSPs use role-based access control checks, which can be specified programmatically or declaratively, similar to ASP.NET. The mapping between authenticated users and security roles is not specified; it happens in a vendor-specific way. However, the servlet specification does standardize ACL declarations by security roles in the web.xml deployment descriptor, which can protect web resources defined as HTTP methods applied to URL-patterns. Also, the transport-guarantee element is considered during requests evaluation. A side effect of this approach is that the resulting declarative access control mechanism is rather coarse, on the file/operation level:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>Restricted Servlets
       </web-resource-name>
    <url-pattern>/myserver/AccountingServlets/*
       </url-pattern>
    <url-pattern>/myserver/FinanceServlets/*
       </url-pattern>
    <http-method>POST</http-method >
    <http-method>GET</http-method >
  </web-resource-collection>
  <auth-constraint>
    <role-name>owner</role-name>
  </auth-constraint>
  <user-data-constraint>
    <transport-guarantee>INTEGRAL
      </transport-guarantee>
  </user-data-constraint>
</security-constraint>

Violation of the auth constraint will result in either the HTTP 401 (if unauthenticated) or HTTP 403 (if authenticated, but ACL-rejected) status code being returned to the caller. For cases of anonymous web users, the web application's deployment descriptor may contain a <run-as> element, which will specify the identity that will be used to process the request. If it is specified, the servlet container is required to propagate this security identity in calls to the EJB layer, whether in the same or in a different J2EE application, as was explained in the Identities section.

Principal checks may be performed imperatively, using one of the methods exposed by HttpServletRequest: getUserPrincipal, getRemoteUser, or isUserInRole. They can be used to provide finer-grained checks than declarative security allows for:

public void doGet(HttpServletRequest request, 
         HttpServletResponse response) {
  java.security.Principal principal = 
           request.getUserPrincipal();
  String user = request.getRemoteUser();
  if (user != null) {
    //have an authenticated user, check his name
  } 

  if (request.isUserInRole("owner")) {
    //owner of the account
  }
}

EJB role-based security is similar to that of servlets, and can be declarative or programmatic. However, the declarative variant is finer-grained, as it allows access control up to the methods level. Mapping of principals to roles is vendor-specific, but the EJB specification dictates role-based ACL format in the bean deployment descriptor, with * as a wildcard for all permissions:

<assembly-descriptor>
  <security-role>
    <description>Role description</description>
    <role-name>UserRole</role-name>
  </security-role>
</assembly-descriptor>

<method-permission>
  <role-name>UserRole</role-name>
  <method>
    <ejb-name>UserAccess</ejb-name>
    <method-name>*</method-name>
  </method>
  <method>
    <ejb-name>OwnerAccess</ejb-name>
    <method-name>getUserInfo</method-name>
  </method>
</method-permission>

Using the <unchecked> element in the bean's descriptor will bypass any authorization, even if the <role-name> element is also specified.

Some methods may even be excluded from being called at deployment time by specifying an exclude list. This list provides a directive from the application assembler to the deployer that these methods should be configured to deny any access:

<exclude-list>
  <method>
    <ejb-name>SomeBean</ejb-name>
    <method-name>problematicMethod</method-name>
  </method>
</exclude-list>

Alternatively, the principal's attributes can be accessed from the bean's code, using methods exposed by the EJBContext class. Note that those methods may be invoked only in the EJB business methods with security context present -- otherwise, a java.land.IllegalStateException will be thrown. Also, both the getCallerPrincipal and isCallerInRole methods from EJBContext always operate on the caller identity, even if the <run-as> attribute was specified.

public class UserAccessBean 
                 implements SessionBean {
  EJBContext beanContext;

  public void getUserInfo() {
    java.security.Principal principal = 
            beanContext.getCallerPrincipal();
    if (beanContext.isCallerInRole("UserRole")) {
      //authenticated user 
    }
  }
}

Note: For extended access checks, both systems provide an adequate level of declarative support.

Conclusions

This article addressed the user authentication and authorization features of the Java and .NET platforms. .NET suffers from tight integration with IIS, without which it is not really capable of performing authentication. In terms of access control, it does provide a convenient mechanism that meshes nicely with its CAS features. Java, in addition to the standard authentication types, offers the powerful JAAS mechanism as its primary vehicle for adding authentication and Principal-based authorization to Java applications, which adds a lot of flexibility to the design choices.

Overall, .NET and Java both earned pretty high marks on the security comparison, which should not be too surprising, considering the long history of Java in the enterprise and the amount of effort that Microsoft is putting into making .NET a premier Windows development platform. Traditionally (and .NET is not an exception), Microsoft products have done best in the closed, homogeneous environment of all-Windows networks, while enterprise Java performs quite well in heterogeneous environments. If we consider an all-Microsoft network, it allows system integration and utilization of .NET's security features to their fullest potential. In case of a mixed environment, Java's platform-independent security features may be more useful than those of .NET. Also, when issues like communication security and authentication across networks start coming into play, Java seems to be a good choice.

Demo Applications

Bibliography

Denis Piliptchouk is a senior technical member of BEA's AquaLogic Enterprise Security group, participates in OASIS WSS and WS-I BSP standards committees, and regularly contributes to industry publications.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.