Java Security FundamentalsTo really understand the security mechanisms available to you in EJB, it would help to understand what security infrastructure is available from the core Java language and where it helps with EJB applications and where it falls short. This section introduces the security aspects of the Java language, but does so from a high enough level as not to complicate our discussion of EJB application security. Although the two have some ties, it's not absolutely necessary to understand the entire Java security model to program enterprise beans. The security architecture in Java has evolved three significant times since it was first created. The changes were primarily made to ease some of the restrictions that were placed on Java applications and applets in the early releases. The Java security model has always been conservative, which you want from a security perspective, but the restrictions came at a price, which made it not so easy to get a consistent security policy for applications and applets alike. Although the use of applets is arguably less than it was in the early days of Java, it still helps to understand the reasoning for the changes. Java 1.0 introduced the security sandbox , which confined untrusted code to run in a very protected area where it could not negatively affect other running systems or system resources. This was necessary because the client browser downloads applets and runs them on the local machine. A client didn't necessarily want an applet to be able to read and write to and from the file system, because severe damage to the user 's data can take place. On the other hand, applications were given free reign to the system resources from a security perspective because they typically were launched locally. A component known as a SecurityManager is responsible for determining on which resources untrusted code is allowed to operate . With Java 1.1, applets were allowed to run out of the sandbox, as long as they were signed with a private key. If an applet was unsigned, it was forced back into the sandbox model. Although this allowed signed applets to have the same possible resource access as an application, it still wasn't very flexible for developers. JDK 1.2 (Java 2) introduced several improvements over the previous Java security models. First and foremost, it added the capability for applications and applets to use security policies in the same manner, which permitted a more flexible and consistent security mechanism for application developers. The Java security policy defines a set of permissions that grant specific access to resources such as the file system or sockets. Listing 14.1 shows some of the permissions in the default policy file that are installed with the SDK 1.3. Some of the lines have been wrapped to make them fit on the page. Listing 14.1 The Default Policy File Installed for SDK 1.3grant codeBase "file:${java.home}/lib/ext/*" { permission java.security.AllPermission; }; grant { permission java.lang.RuntimePermission "stopThread"; permission java.net.SocketPermission "localhost:1024-", "listen"; permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.vendor", "read"; permission java.util.PropertyPermission "java.vendor.url", "read"; permission java.util.PropertyPermission "java.class.version", "read"; permission java.util.PropertyPermission "os.name", "read"; permission java.util.PropertyPermission "os.version", "read"; permission java.util.PropertyPermission "os.arch", "read"; permission java.util.PropertyPermission "file.separator", "read"; permission java.util.PropertyPermission "path.separator", "read"; permission java.util.PropertyPermission "line.separator", "read"; permission java.util.PropertyPermission "java.specification.version", "read"; permission java.util.PropertyPermission "java.specification.vendor", "read"; permission java.util.PropertyPermission "java.specification.name", "read"; permission java.util.PropertyPermission "java.vm.specification.version", "read"; permission java.util.PropertyPermission "java.vm.specification.vendor", "read"; permission java.util.PropertyPermission "java.vm.specification.name", "read"; permission java.util.PropertyPermission "java.vm.version", "read"; permission java.util.PropertyPermission "java.vm.vendor", "read"; permission java.util.PropertyPermission "java.vm.name", "read"; permission java.net.SocketPermission "1024-65535", "accept, connect, listen, resolve"; permission java.net.SocketPermission "localhost", "accept, connect, listen, resolve"; }; The runtime system structures code into individual groups called security domains . Each domain contains a set of classes and, because permissions are defined at the domain level, all the classes within a particular domain have the same access permissions. This allows for a much more flexible security model, while at the same time allowing for configuration similar to the sandbox approach. By default, applications still have unlimited access, but if required, they can be constrained within a domain by using a security policy and installing a SecurityManager for the application. You can specify a SecurityManager for an application either by supplying one on the command line as a system property or by setting one up programmatically at the start of the application. By using security policies, the security implementation can be separated from the policy. Figure 14.2 shows a diagram of the Java 2 security architecture. Figure 14.2. The Java 2 security architecture.
The Java ClassLoaderThe Java ClassLoader is responsible for loading Java byte codes into the Java Virtual Machine (JVM). It partners with the AccessContoller and the SecurityManager to ensure that the security policies are not violated. There are different types of class loaders, and third-party components can create a customer class loader to provide security features beyond those offered by the Java 2 standard security model. One very important version of the class loader is called the "System" ClassLoader . This type of class loader helps launch the initial JVM by reading in classes and packages that are essential in starting the runtime system. Permission ClassesPermission classes are at the root of the Java security model. They allow or deny access to the system resources such as files, sockets, RMI objects, and so on. The set of permissions, when mapped to classes, can be conceptually thought of as the security policy for an application. A security policy file is used to configure the security rules for an application. The security policy file is a text file that can be viewed or edited by hand or by using the policy tool located in the bin directory of the Java home directory. The Java SecurityManagerThe SecurityManager checks to ensure that the action that is being requested does not violate the security policies established in the security policy for an implementation. The SecurityManager works with the AccessController to verify whether the permission should be granted or denied . If an unauthorized permission is attempted, it is the job of the SecurityManager to raise a security exception back to the requesting component. The AccessController ClassThe AccessController class decides whether access to a system resource should be granted or denied based on the current security policy being used. The AccessController also has several static methods that can be used by an application to help check whether the calling component has the proper permission to access a resource. An AccessControlException will be raised if access is denied. The AccessControlContext ClassIn normal situations, the SecurityManager delegates permission checks down to the AccessController class. The AccessController uses the context within the current thread to determine whether to grant the permission. In some situations, however, it's necessary to do work in a separate thread but still maintain the proper security context. This is where the AccessControlContext class can help. For example, if you needed to create a worker thread and allow it to have the same permissions as the parent thread, you can create an AccessControlContext object from the AccessController and pass it on to the worker thread to use for permission checks. This concept of obtaining the security context from the current thread and passing it or propagating it on to another thread will become very important when we talk about how J2EE containers propagate security information from one container to another during remote calls. Privileged CodeAs the previous sections explained, the policy for an installation specifies what resources can be accessed based on the set of permissions for a protection domain. It sometimes is necessary for an application to override these restrictions and perform an otherwise unauthorized action. Marking code as privileged enables a piece of trusted code to temporarily grant access to more resources than are available directly to the code that called it. Whenever a resource access is attempted, all the code that is called by the execution thread must have permission to access the particular resource, or an AccessControlException will be thrown. If the code for any caller in the call chain doesn't have the requested permission, the exception is thrown, unless one of the callers whose code does have the permission has been marked as privileged and all the callers called after this caller also have the permission. To mark code as privileged, you can use the doPrivileged feature located on the AccessController class. The following code fragment illustrates how you might mark some code as privileged: public class MyPrivilegedAction implements java.security.PrivilegedAction { public MyPrivilegedAction() { super(); } public Object run(){ // privileged code would go here FileInputStream stream = new FileInputStream( "aFile" ); // do some work with the file // Nothing to return } } // In some other class here public void someMethod(){ // Other code here MyPrivilegedAction action = new MyPrivilegedAction(); // Changed to privileged java.security.AccessController.doPrivileged( action ); // Once the privileged action finished, back to normal mode } If you need to return a value from the run method, you'll need to cast it to the correct class stereotype. If the code in the run method might possibly throw a checked exception such as a FileNotFoundException , you will need to use the PrivilegedExceptionAction instead. The following code fragment illustrates how this might be handled: public class MyPrivilegedAction implements PrivilegedExceptionAction { public MyPrivilegedAction() { super(); } public Object run(){ // privileged would go here // Nothing to return } } public void someMethod() throws java.io.FileNotFoundException { // Other code here MyPrivilegedAction action = new MyPrivilegedAction(); // Changed to privileged try{ FileInputStream inStr = (FileInputStream)java.security.AccessController.doPrivileged( action ); //Once the privileged action finished, back to normal mode // The PrivilegedActionException is just a wrapper around the // real exception that occurred. }catch( PrivilegedActionException ex ){ // Assuming a FileNotFoundException although you might really // want to check for this to be safe throw (FileNotFoundException)ex.getException(); } } When the privileged code is finished, the application should go back to the normal policy and permission use. Be very careful when using this feature, and keep the section of code that is executing as privileged as small as possible to prevent security holes. |