ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Security in Struts: User Delegation Made Possible

by Werner Ramaekers
02/18/2004

The Jakarta Struts framework has been widely adopted for creating web applications in the J2EE world. Struts makes it easy to create a web application because it builds on standard J2EE technologies like Java servlets, JavaBeans, JavaServer Pages, and custom tags. Validation of user input and internationalization of the views on the application are some of the important and better-known reasons to choose Struts as the basis for your web application.

However, Struts does not pretend to offer you a complete solution for all of your web application needs; that has never been the goal of the project. The developers of the framework have chosen to make Struts very extensible: you, as an application developer, can easily replace any of the classes that are deployed as part of the Struts framework to provide the missing pieces you need for your application's architecture.

What if you need to have the possibility for certain users of your application to delegate their actions to users under their control? What if you have just built a great human resources management application, but the human resources manager comes back to you and explains that he wants to be able to authorize different access levels to the employees in his department -- user X must be able to view Action A but cannot edit it, and user Y must be able to view and edit Action B but cannot be granted any authorization on Action B? Can you solve this kind of delegation of security responsibilities with Struts? Will you dive into your code and implement it programmatically, or is there a more generic way of adding this functionality to your application?

Struts Security Configuration

Related Reading

Jakarta Struts Pocket Reference
By Chuck Cavaness, Brian Keeton

In struts-config.xml you can specify a roles attribute, a comma-delimited list of security role names that are allowed access to the ActionMapping object in question. But that is all there is available regarding security in Struts, and it is surely not sufficient to implement the security delegation the application needs.

This example will show you how to extend Struts so that your application can do the following:

  • Each ActionMapping is identified by an ID.
  • Each ActionMapping can be configured as "made available for delegation."
  • Each ActionMapping for which delegation is available can be made available to a lower user security level in read, write, or both.
  • The ActionMapping configuration is retrieved after Struts has been deployed.
  • The user permission configuration that results from the usage of the delegation service can be persisted in the database.
  • The user gets only access to those parts of the application for which he was granted access by his superior.
  • Easy management of application and security, no duplication of content.

Extending the Struts ActionMapping class

Since the ActionMapping class included with the default Struts implementation does not provide the necessary attributes to enable these kinds of security features, we need to extend it with our own ActionMapping:

public class StrutsPermissionMapping 
        extends ActionMapping {

    private Integer actionId = null;
    private String label = null;
    private String canBeMadeAvailable = null;
    private String canBeMadeEditable = null;
    private String group = null;
    private String role = null;

    public StrutsPermissionMapping() {
        super();
    }

    public Integer getActionId() {
        return actionId;
    }

    public void setActionId(Integer id) {
        this.actionId = id;
    }    
...
}

Each of the extra attributes is declared according to the JavaBeans specification: the attribute itself is declared private and it is accessed using public getXXX() and setXXX() methods. The information for the defined attributes can now be added to struts-config.xml.

This is an extract of an example struts-config.xml that shows how the StrutsPermissionMapping class is used:

<struts-config>
 <form-beans>
   <form-bean name="computeForm"
  	type="com.shiftat.oreilly.web.ComputeForm"/>
...
 </form-beans>
 <action-mappings> 
  <action
   path="/compute"
   type="com.shiftat.oreilly.web.ComputeAction"
   name="computeForm"
   scope="session"
   input="/jsp/compute.jsp"
   className=
    "com.shiftat.struts.StrutsPermissionMapping"
   unknown="false"
   validate="false">
   <set-property property="actionId" 
                 value="160" />
   <set-property property="label" 
                 value="compute"/>
   <set-property property="canBeMadeAvailable" 
                 value="true"/>
   <set-property property="canBeMadeEditable" 
                 value="false"/>
   <set-property property="group" 
                 value="4"/>
   <set-property property="role" 
                 value="4"/>

   <forward name="succes" 
            path="/jsp/result.jsp"
	        redirect="false"/>
  </action>
...
 </action-mappings>
</struts-config>

The action implemented in the ComputeAction class has the actionId of 160; it will be shown with the label compute and it can be delegated (canBeMadeAvailable is true) but only in read-only mode (canBeMadeEditable is false). The delegation can be executed by users in group 4 and role 4. Extra attributes that are defined in our StrutsPermissionMapping but not in ActionMapping are specified using the set-property tags, which allows the Struts configuration parser to pick up the extra attributes. The values for the actionId (160), the group (4), and the role (4) are only relevant to the specifics of my application and will, of course, be different in your own application's configuration.

As an experienced J2EE developer, you probably hate repeating yourself, so you will probably be generating the struts-config.xml using XDoclet as an important step in your build process. The approach based on the generation of the extra set-property tags explained in this article is fully supported by XDoclet. The JavaDoc for the ComputeAction declaration would look like this when XDoclet is used to generate the struts-config.xml file:

@struts.action
  className
    ="com.shiftat.struts.StrutsPermissionMapping" 
    path="/compute"
    input="/jsp/compute.jsp"
    name="computeForm"
    scope="session"
    validate="false"
    
@struts.action-set-property
    property="actionId"
    value="160"
 
@struts.action-set-property
	property="label"
    value="compute"
  
@struts.action-set-property
	property="canBeMadeAvailable"
    value="true"
 
@struts.action-set-property
	property="canBeMadeEditable"
    value="false"

@struts.action-set-property
	property="group"
    value="4"
 
@struts.action-set-property
	property="role"
    value="4" 

@struts.action-forward
   name="succes"
   path="/jsp/result.jsp"
   redirect="false"

Getting Hold of the Action Permissions

Now that we know how to extend the Struts ActionMapping to provide our extra security-related attributes, we need to get hold of the StrutsPermissionMapping instances once the application is deployed, because:

  • We need to be able to present the users that have the permission to delegate actions a list of actions that can be delegated.
  • We need to retrieve the list of allowed actions when a restricted access user logs in.

Since we decided to extend the Struts framework by implementing an extension of the ActionMapping class, we will rely on Struts to do a lot of the work in loading and creating the configured ActionMapping class instances. How does all of this work? We will look at how Struts translates the struts-config.xml file into classes so that we can reuse what is already available instead of writing our own XML parsing class to get hold of the security-related parameters we created above.

When a web container loads a web application, it will look at the web.xml deployment descriptor to know what the configuration after deployment should be. When the web application is built using the Struts framework, the web.xml file will contain a reference to the configuration for the Struts ActionServlet that will point to the struts-config.xml file. The part of web.xml that is relevant to the Struts configuration looks like this:

<web-app>
...
  <servlet>
    <servlet-name>
	  action
	</servlet-name>
    <servlet-class>
	  org.apache.struts.action.ActionServlet
	</servlet-class>
    <init-param>
      <param-name>
       application
     </param-name>
     <param-value>
       ApplicationResources
     </param-value>
    </init-param>
    <init-param>
      <param-name>config</param-name>
      <param-value>
        /WEB-INF/struts-config.xml
      </param-value>
    </init-param>
    <init-param>
      <param-name>debug</param-name>
      <param-value>1</param-value>
    </init-param>
    <init-param>
      <param-name>detail</param-name>
      <param-value>1</param-value>
    </init-param>
    <init-param>
      <param-name>validate</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
       <param-name>nocache</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
  </servlet>

  <!-- Struts -->
  <!-- Action Servlet Mapping -->
  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

...
</web-app>

The class in the Struts framework that takes care of translating the struts-config.xml file into objects is org.apache.struts.action.ActionServlet. This class translates the mappings and configurations described in the configuration file into objects, and stores these objects in the ServletContext of the ActionServlet. This means that once the web application is deployed, we can look up the configuration that was created from our struts-config.xml from a servlet, as follows:

  ModuleConfig mConfig = 
    this.getServletContext()
		 .getAttribute(Globals.MODULE_KEY);

The ModuleConfig and Globals classes are part of the standard Struts framework, which we will reuse from our own application.

Since we need to show the user a list of actions which he can make available to another user, we need to get hold of the ModuleConfig object and ask for the ActionMapping objects that are instances of our StrutsPermissionMapping class. The retrieveStrutsActionMapping method of StrutsConfigurationHelperAction presented next will return the Map of StrutsPermissionMapping instances that is available in the application:

public class StrutsConfigurationHelperAction {
    
 private static SortedMap actionMappingMap = null;
 private static ModuleConfig mConfig = null;
    
 public static SortedMap 
         retrieveStrutsActionMapping(Action action, 
                     HttpServletRequest request) {
   if (actionMappingMap == null){
       actionMappingMap = new TreeMap();
       mConfig = (ModuleConfig)request.
                   getAttribute(Globals.MODULE_KEY);
       if (mConfig == null){
           mConfig = (ModuleConfig)action.
             getServlet().getServletContext().
               getAttribute(Globals.MODULE_KEY);
       }
       if (mConfig != null){
           ActionConfig[] acfg 
               = mConfig.findActionConfigs();
           for (int i=0; i < acfg.length; i++){
              ActionConfig actionConfig = acfg[i];
              if (actionConfig instanceof 
                      StrutsPermissionMapping){
                  StrutsPermissionMapping amp = 
					 (StrutsPermissionMapping)
					       actionConfig;
                   actionMappingMap
				      .put(amp.getActionId(),amp);
               } else {
                   //Regular ActionMapping 
                   //without security attributes
               }
           }
       } else {
          System.err.println
		  		("No Struts configuration !");            
       }
   }
   return actionMappingMap;
 }

}

ModuleConfig, Globals, and ActionConfig are classes that are part of the standard Struts framework. The action mapping configuration can be obtained from the ModuleConfig object using the findActionConfigs method; this method returns an Array of ActionConfig objects that are an instance of the regular ActionMapping class or of the StrutsPermissionMapping class. The code then iterates through the Array, and if the ActionConfig object is an instance of our special StrutsPermissionMapping, it is put into the Map. The regular ActionMapping objects are not put into the Map, since they are not available for delegation, as they do not have any security attributes attached.

The helper class will only retrieve the Struts permissions once from the Struts configuration since the Struts configuration is only instantiated when the application is deployed; it makes no sense to check if the configuration has changed if the application was not redeployed.

Configuration and Use of the Security Delegation

All building blocks are now in place to allow users of a certain group and role to delegate permissions to the users for which they are responsible. Using these building blocks, an application can show to a person in the "department manager" role the permissions he can attribute to the members of his department who are in a role lower than manager. The permissions that were attributed by the manager can then be persisted in a datastore using any persistence mechanism, be it EJB or Hibernate (to name just two of the well-known solutions). It is sufficient to store the userId, the actionId, and the permission (read, write or read/write), as we will see later in this section.

Upon login, the user will not only need to be authenticated into the application, but his permissions will also need to be retrieved from the datastore when he is granted access. In the UserLoginAction class, you will find code that does the following:

  1. Retrieves the user permissions from the datastore.
  2. Retrieves the StrutsPermissionMappings from the Struts configuration.
  3. Iterates over the user permissions and retrieves the corresponding StrutsPermissionMappings.
  4. Stores each of the corresponding StrutsPermissionMappings in a new Map in the context for that user.
Map userActionPermissionMap 
  = retrievePortalUserActionPermissionMap(userId);
Map strutsConfigMap 
  = StrutsConfigurationHelperAction
    .retrieveStrutsActionMapping(this, request);
Map userActionNamePermissionMap = new HashMap();
if (userActionPermissionMap.keySet() != null 
 && userActionPermissionMap.keySet().size() >0) {
  Iterator it 
   = userActionPermissionMap.keySet().iterator();
  while (it.hasNext()){
	Integer actionId = (Integer)it.next();
	Integer permissionId 
	 = (Integer)userActionPermissionMap
	   .get(actionId);
	StrutsPermissionMapping mapping 
	 = (StrutsPermissionMapping)strutsConfigMap
	   .get(actionId);
	String actionPath 
	   = strutsPermissionMapping.getPath();
	userActionNamePermissionMap
	   .put(actionPath, permissionId);
  }
}
context
 .setAttribute("permissionmap",
               userActionNamePermissionMap);

Once the user has been successfully authenticated and his permissions have been stored in his session context, the application can, upon each call to an Action, verify if the user has been granted access to the action being called. The check that the user has the necessary permission to call a certain action in the application can easily be done in a ServletFilter, but other solutions are possible. For example, using the Tiles framework, it is also possible to create a special, tabbed-layout JSP that will hide the tabs that the user is not granted access to; you could create a tag library that provides you the tags for the use in the layout JSP. Just choose the approach which fits your architecture best.

Conclusion

Jakarta Struts is not the web application framework that will solve all of your web application needs, but that was never its intent. Since Struts is easily extensible, it is not very hard to add the missing parts yourself. We have shown in this article that creating a user security-delegation extension to Struts only requires an extension of the ActionMapping class.

Struts Resources

Werner Ramaekers has been architecting J2EE projects using JBoss since way back in 2000. You can follow his ongoing projects through his blog


Return to ONJava.com.