Sunday, January 17, 2010

Using JAAS auhtorization to protect business objects

You will frequently a scenario in your application where different roles should be permitted access to different business objects.  
Consider an application where different types of users can save different types of content e.g. Audio users can save audio content; Text users can save text content only and similarly. Admin can save whatever it wants. You can apply these constraints in JAAS.


grant principal weblogic.security.principal.WLSGroupImpl "audioUser" 
{
 permission com.test.security.ResourcePermission "com.test.entity.audio.*", "create,read,update,delete";
}; 

grant principal weblogic.security.principal.WLSGroupImpl "textUser" 
{
 permission com.test.security.ResourcePermission "com.test.entity.text.*", "create,read,update,delete";
}; 

grant principal weblogic.security.principal.WLSGroupImpl "admin" 
{
 permission com.test.security.ResourcePermission "*", "create,read,update,delete";
}; 



Note:
1- I put all business objects permitted to audioUser in package com.test.entity.audio.* and similarly for textUser. I achieved these benefits using BasicPermission
2- I have used weblogic.security.principal.WLSGroupImpl for my role principals because i was using Weblogic authentication.
3- You can specify above constraints in java.policy. If you are using Weblogic you can also specify it in weblogic.policy but it depends on how you are staring weblogic server either:
-Djava.security.policy==%WL_HOME%\server\lib\weblogic.policy
OR
-Djava.security.policy=%WL_HOME%\server\lib\weblogic.policy
== enforces only weblogic.policy to be used, whereas = appends weblogic.policy to other policies specified in java.security.
4- To use JAAS authorization you need not enable Security Manager. And i would recommend not to do so as enabling it will also make weblogic code and other applications to pass through security checks and you may get all sorts of exceptions.

Usually code applies authorization checks like below:

if(System.getSecurityManager() != null){
 System.getSecurityManager().checkPermission(...);
}

So disabling security manager also disables authorization checks. Instead of above i would apply authorization directly using AccessController.

To authorize update operation you would:









AccessController.checkPermission(weblogic.security.Security.getCurrentSubject(), new ResourcePermission(this.getClass()
.getCanonicalName(), EnumSet.of(Action.update)));


Or better yet put above code of checking permission in Subject.doAs() so as to avoid protection domain checks even if security manager is enabled.




Now the code, you require just two classes to implement this. You do not need to place below classes in JAVA_HOME/lib/ext, you can put these in your project. This is the magic of UnresolvedPermission.










package com.test.security;


import java.security.BasicPermission;
import java.security.Permission;
import java.util.EnumSet;


public class ResourcePermission extends BasicPermission {
private EnumSet<Action> actionSet;


public ResourcePermission(String name, String actions) {
super(name == null ? "" : name);
actionSet = Action.getActionSet(actions);
}


public ResourcePermission(String name, EnumSet<Action> actionSet) {
super(name == null ? "" : name);
this.actionSet = actionSet;
}


@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof ResourcePermission) {
ResourcePermission other = (ResourcePermission) obj;
return super.equals(obj) && actionSet.equals(other.getActionSet());
}
return false;
}


@Override
public String getActions() {
return Action.toString(actionSet);
}


public EnumSet<Action> getActionSet() {
return actionSet;
}


@Override
public int hashCode() {
return actionSet.hashCode() + getName().hashCode();
}


@Override
public boolean implies(Permission permission) {
if (permission != null && permission instanceof ResourcePermission) {
ResourcePermission other = (ResourcePermission) permission;
return super.implies(permission)
&& actionSet.containsAll(other.getActionSet());
}
return false;
}
}


///////////////////////////////////////////////////////////////////////////////////////


package com.test.security;


import java.util.EnumSet;


public enum Action {
create, update, delete, read;


public static EnumSet<Action> getActionSet(String serializedActions) {
EnumSet<Action> actionSet = EnumSet.noneOf(Action.class);
if (serializedActions != null) {
String[] serializedActionsSet = serializedActions.split(",");
for (String serializedActionItem : serializedActionsSet) {
actionSet.add(Action.valueOf(serializedActionItem));
}
}
return actionSet;
}


public static String toString(EnumSet<Action> actionSet) {
StringBuilder actions = new StringBuilder();
for (Action action : actionSet) {
actions.append(action.toString() + ",");
}
if (actions.lastIndexOf(",") == actions.length() - 1) {
actions.replace(actions.length() - 1, actions.length() - 1, "");
}
return actions.toString();
}


}


Understanding Weblogic class loading



Yes! You need to understand class loading. I was also of the view that why do I need to go into the details of weblogic class loading, can’t I just develop and deploy j2ee applications on weblogic. And then you encounter following kind of problems
1-      Log4j is not working as expected, although you have configured it correctly.
2-      You get ClassCastException although you have included the library in your application.

I will explain the problems and solutions later, first class loading.

Class loading is a task of loading java classes and is performed by class loaders.  Class loaders are arranged in inheritance hierarchy this is called Delegation. Each class loader first requests super class loader to load requested class and if super fails it tries to load itself. In this way class loaders do not load classes already loaded by parent class loaders.

J2SE defines following levels of class loaders.  Please refer to figure 1.

1- 1-      Bootstrap class loader: is native code and its responsibility is to load JAVA_HOME/lib and JAVA_HOME/lib/ext.
2-  2-     System class loader: loads classes in CLASSPATH.

Weblogic replaces system class loader with its own version.
1-      Weblogic system class loader, loads weblogic classes.
2-      For each enterprise application (EAR) deployed on weblogic there are two instances of class loaders one for EJB and other for Web app as its child. For an EJB application only EJB Class loader is used and similarly for Web app. EJB Class loader is parent of Web class loader as typically web classes use EJBs. Note that there are separate instances of these class loaders for each deployed weblogic application, referred in docs as sibling class loaders. So, that classes loaded by one weblogic application are not visible to others. This enables weblogic applications to use different versions of single class / library.

If you create an enterprise application project, EJB Class loader loads classes/libraries of both EJB project and EAR project. Whereas Web class loader loads classes/libraries of web project only.  Suppose you have an enterprise project with following structure.

Enterprise Project
                Ear-Content
APP-INF
lib
ep_x1.jar
ep_x2.jar
Web Project
                Web-Content
Web-INF
lib
wp_x1.jar
ep_x1.jar
ej_x1.jar
ejbModule

Considering jars only, EJB Class loader will load ep_x1.jar, ep_x2.jar and ej_x1.jar. Web class loader will only load wp_x1.jar. And when it tries to load ep_x1.jar it will find out that its classes are already loaded by its parent class loader so it reuses.

Now let us add log4j support in your project. In order to avoid repetition you should add log4j.jar in Enterprise project’s APP-INF/lib folder and reference it from both Web and EJB projects. Do the same for all of your common libraries. What about log4j.properties? Where should we place the file? As you may know log4j automatically loads log4j.properties from CLASSPATH on startup (static loading). And in our current configuration log4j is loaded by EJB Class loader so we should place single properties file in EJB project.

EJB Project
log4j.properties
ejbModule


What if log4j is being used in weblogic e.g. someone placed it in {$DOMAIN_HOME}/lib folder? Since log4j is already loaded by weblogic system class loader, your project’s log4j.properties will not be statically loaded. Solution is to reload log4.jar but how? Here comes weblogic Filter class loader. In your enterprise project’s weblogic-application.xml add below lines

<wls:prefer-application-packages>
    <wls:package-name>org.apache.log4j.*</wls:package-name> 
</wls:prefer-application-packages>

Filter Class loader throws ClassNotFound exception on each of the classes configured as above, thus allowing child class loaders to themselves load classes. And this will solve our problem as EJB Class loader will load log4j.jar irrelevant of whether it is already loaded by parent class loaders and therefore allowing log4j to read our supplied log4j.properties.