Security in Struts: User Delegation Made Possible
by Werner Ramaekers02/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 |
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
ActionMappingis identified by an ID. - Each
ActionMappingcan be configured as "made available for delegation." - Each
ActionMappingfor which delegation is available can be made available to a lower user security level in read, write, or both. - The
ActionMappingconfiguration 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:
- Retrieves the user permissions from the datastore.
- Retrieves the
StrutsPermissionMappings from the Struts configuration. - Iterates over the user permissions and retrieves the corresponding
StrutsPermissionMappings. - Stores each of the corresponding
StrutsPermissionMappings in a newMapin 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
- Struts community resources
- Sample code from this article (.zip format)
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.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 9 of 9.
-
Using tiles to create role driven tabbed-layout?
2004-08-21 21:55:00 ebegoli [Reply | View]
Warner,
In the closeout statement of your otherwise excellent article you mention that:
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.
Can you by any chance help me locate the example
that does something like that. I would like to know how to use tiles to dynamically hide/show links to actions accesible by the role (also dynamically created and with delegated rights as your article suggests).
Any help or hint is greatly appreciated.
Regards,
Edmon
-
Using tiles to create role driven tabbed-layout?
2004-08-22 14:47:28 wernerramaekers [Reply | View]
Edmon,
the example code i provided does not offer an example of dynamic showing and hiding of links accessible by a role when using a tabbed-layout JSP.
However, it is quite easy to create this kind of behaviour starting from the tabsLayout.jsp that you get as part of the available layouts in the tiles-documentation.war in the struts-1.1 binary download.
Oh, and by the way my first name is WErner ;-)
Cheers,
Werner
-
What is the difference to setting the roles at the action level
2004-04-15 17:19:06 stefan88 [Reply | View]
The approach you describe eventually allows to plug in one's own permission system at the Action level and read the entire permission-action matrix from everywhere. But then you also say that assignment of permission is static after the server is started. So where is the gain?
I can already assign roles to Actions since Struts 1.1. The roles are application specific, and I have to know their names anyway. I can use the AS user provisioning to assign roles to users (Tomcat 5 has a nice Admin interface to assign roles to users and groups). I cannot have users delegate roles there, but the software you describe here cannot do this either. You just say that this can be done elsewhere, but this outside application could also set Servlet roles (permissions) in a hierachical way. Where is conext to your example and what's the gain ?
Please advise, I might be missing something.
cannot do this either. You say that one can do that elsewhere, but your article is not about a permission system.
-
What is the difference to setting the roles at the action level
2004-04-18 03:10:11 wernerramaekers [Reply | View]
The article is not about role-based permissions but about delegating permissions to users beloging to the same group but who are in a different role.
The extension i specify here allows for users, like say the Manager of a certain department, to decide if he wants member x of his/her department to be able to view/edit action y. And if member a can view/edit action b. At least that is how it is used on one of the projects in production already ;-)
Please read the article again and hopefully it will all become clear to you.
Werner
-
checking constraints
2004-02-18 23:54:55 yannc76 [Reply | View]
The check if the user has the necessary roles to execute the current action could also be moved to the RequestProcessor-class (Struts 1.1+), thereby freeing the Action-classes from the security-logic. -
checking constraints
2004-02-19 00:18:26 wernerramaekers [Reply | View]
I agree with you that the Action classes should not be bothered with the security-logic. The article explains how to declare the security-logic configuration but leaves the choice of the class where you verify that the user has the necesary permission up to the designer as explained in the paragraph :
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.
The RequestProcessor is indeed a good location but a ServletFilter is equally suited, it just depends in what the flexibility is that you need. You might want to be able to switch off the security checks during development by commenting out the mapping of the ServletFilter in the web.xml but other solutions are certainly possible.





How does the method, public static SortedMap retrieveStrutsActionMapping(Action action, HttpServletRequest request) behave in a multiuser, concurrent request environment? I am looking at this solution seriously for my project.