Monday Oct 01, 2007
Monday Oct 01, 2007
Java EE allows you to protect web resources through declarative security, but this approach does not allow you to protect local beans used by servlets and JavaServer Pages (JSPs). Also, although you can protect JavaServer Faces technology (JSF) pages using declarative security, this is often not sufficient.
This tip will show you a way to extend JSF security configuration beyond web pages using managed bean methods.
Introduction
Java EE allows you to protect web pages and other web resources such as files, directories, and servlets through declarative
security. In this approach you declare in a web.xml file specific web resources and the security roles that can access those resources. For example, based on the following declarations in a web.xml file, only authenticated users who are assigned the admin security role can access the secured resources identified by the URL pattern /members.jsf:
<security-constraint> <display-name>Sample</display-name> <web-resource-collection> <web-resource-name>members</web-resource-name> <description/> <url-pattern>/members.jsf</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <description/> <role-name>admin</role-name> </auth-constraint> </security-constraint> <security-role> <description/> <role-name>admin</role-name> </security-role>
Notice that you identify the resources you want to protect by specifying their URLs in a <url-pattern> element. Unfortunately, because local beans used by servlets and JavaServer Pages (JSP) cannot be mapped to a <url-pattern> element, you can't use declarative security to protect local beans.
Also, although you can protect JSF pages using declarative security, this is often not sufficient. For example, you might want a JSF application to present the same page to users with different roles, but only allow some of those roles to perform specific operations. For instance, you might allow users with all of those roles to read and update data, but allow users with specific roles to create and delete data. In that case, you need a way to extend JSF security beyond web pages.
Additionally, declarative security doesn't check roles during the request processing commonly used by MVC frameworks and JSF. As a result, a managed bean can return any view id even if it's for a protected resource. This can potentially expose protected resources to a role that should not have access to them.
One solution is to use JBoss Seam Web Beans or JSR 299: Web Beans. Web Beans allow you to configure page security, component security, and even Java Persistence Architecture entity security. However, many companies are adopting simpler security solutions without Seam, Spring, EJB, or security-specific frameworks.
The technique covered in this tip demonstrates a simple approach that extends JSF security using annotations in managed beans methods. A sample application accompanies this tip. The code examples in the tip are taken from the source code of the sample application.
Declare the Extended JSF ActionListener and NavigationHandler
To provide managed bean method protection you need to declare the extended JSF ActionListener and NavigationHandler. These
custom classes analyze each user action and check for authentication and authorization.
To enable the classes, you declare the following elements inside the faces-config.xml file:
<!-- JSF-security method--> <application> <action-listener> br.com.globalcode.jsf.security.SecureActionListener </action-listener> <navigation-handler> br.com.globalcode.jsf.security.SecureNavigationHandler </navigation-handler> </application>
SecureActionListener intercepts calls to managed bean methods and checks for annotated method permissions. NavigationHandler forwards the user to a requested view if the user has the required credentials and roles.
For example, the following code renders a JSF page with a View button and a Delete button.
<h:form id="sampleSecurity"> <h:commandButton value="View" id="unprotectedButton" action="#{CustomerCRUD.view}"/> <h:commandButton value="Delete" id="protectedButtonprotectedButton" action="#{CustomerCRUD.delete}"/> </h:form>
When the user clicks on the Delete button, a call is made to the CustomerCRUD.delete method. The method includes an annotation that declares a required role for the method.
public class CustomerCRUD { public String view() { return "view-customer"; } @SecurityRoles("customer-admin-adv, root") public String delete() { System.out.println("I'm a protected method!"); return "delete-customer"; } ...
SecureActionListener intercepts calls to CustomerCRUD.delete and checks for the customer-admin-adv and root permissions. NavigationHandler forwards the user to a requested view if the user has the required credentials and roles.
Set Up User Object Providers
By adding a context parameter into web.xml, you can set up
different user object providers, as follows:
ContainerUserProvider: Integrate with container/declarative security.SessionUserProvider: Look up Http session for object named "user".UserProvider interface: <context-param> <param-name>jsf-security-user-provider</param-name> <param-value> YourClassImplementsUserProvider </param-value> </context-param>Set Up the ContainerUserProvider
The web container provider approach is integrated with declarative security, so it can be used with applications that already use declarative security. Add the following context parameter to set up the default container user provider:
<context-param> <param-name>jsf-security-user-provider</param-name> <param-value> br.com.globalcode.jsf.security.usersession.ContainerUserProvider </param-value> </context-param>Here is what the default web container user provider class looks like:
public class ContainerUserProvider implements UserProvider { ContainerUser user = new ContainerUser(); public User getUser() { if(user.getLoginName()==null || user.getLoginName().equals("")) { return null; } else { return user; } }
ContainerUserProvider references the ContainerUser class. Here's what the ContainerUser class looks like (some of the code lines are cut to fit the width of the page):
public class ContainerUser implements User { public String getLoginName() { if(FacesContext.getCurrentInstance().getExternalContext(). getUserPrincipal()==null) return null; else return FacesContext.getCurrentInstance(). getExternalContext().getUserPrincipal().toString(); } public boolean isUserInRole(String roleName) { return FacesContext.getCurrentInstance().getExternalContext(). isUserInRole(roleName); }Using a SessionUserProvider
If your solution uses a custom security authentication and authorization process, you can provide a user class adapter that implements the given user interface and bind a user object instance into the HTTP Session with the key name "user". This approach works well for legacy Java EE or J2EE applications that don't use declarative security.
Follow these steps to set up your application to use a SessionUserProvider:
web.xml file to
set up the user provider to look up the HTTP Session for the
"user"object: <context-param> <param-name>jsf-security-user-provider</param-name> <param-value> br.com.globalcode.jsf.security.usersession.SessionUserProvider </param-value> </context-param> User class adapter implementation: package model; public class MyUser implements br.com.globalcode.jsf.security.User { //Your user instance object public String getLoginName() { //your user bridge return "me"; } public boolean isUserInRole(String roleName) { //your user roles bridge return true; } } //Login page <h:form id="loginForm"> <h:outputText value="Login:"/> <h:inputText value="#{LoginMB.userName}"> </h:inputText>
<h:outputText value="Password:"/> <h:inputText value="#{LoginMB.password}"/>
<h:commandButton value="Login" action="#{LoginMB.login}"/> <h:messages/> </h:form> <navigation-case> <from-outcome>login</from-outcome> <to-view-id>/login.xhtml</to-view-id> </navigation-case> public class LoginMB { private String userName; private String password; @SecurityLogin public void login() { //Your login process here... MyUser user = new MyUser(); HttpSession session = (HttpSession) FacesContext.getCurrentInstance(). getExternalContext().getSession(false); session.setAttribute("user", user); } }Running the Sample Code
A sample package accompanies this tip. This sample runs with aSessionUserProvider and has a very simple user and login page.
To install and run the sample:<sample_install_dir>/facesannotations-glassfish, where <sample_install_dir>
is the directory where you installed the sample package. For
example, if you extracted the contents to C:\ on a Windows
machine, then your newly created directory should be at
C:\facesannotations-glassfish.faces-config.xml file in the expanded sample
package contains the declarations for the
SecureActionListener and SecureNavigationHandler.facesannotations-glassfish project as follows:facesannotations-glassfish directory from the
sample application download.facesannotations-glassfish as follows:facesannotations-glassfish node in the
Projects window. http://localhost:8080/facesannotations-glassfish/index.jsf
|
|
About the Author
Vinicius Senger is a performance researcher, Java EE architect, and instructor. He started his career at Sun Microsystems and Oracle as independent consultant and official instructor, and later founded Globalcode, a leading Java-related training company in Brazil. Vinicius is a member of the JSF 2.0 Expert Group, the leader of the Global Education and Learning Community, a NetBeans Dream Team Member, and project leader of JAREF, an educational and research framework. He is also a Sun Certified Enterprise Architect and Programmer P1.
good to know, need more of the same , with more detail.
Posted by Lou Blocker on October 19, 2007 at 08:31 AM PDT #
All the source code is hosted at facesannotation.dev.java.net
Posted by Vinicius Senger on November 05, 2007 at 08:45 AM PST #
Excellent excellent work. I see that as edge technology. I was searching for a good, alternative way to secure my web apps, in a programmer's friendly way. Either in jsp,jsf or visual jsf I thought that the best way to control which components should appear to the user is something like this : a hashtable containing user roles as keys and lists of components Ids as values. So, every request on a page should end up traversing a list depending on the role(s) of the current user, marking as visible or rendered the components contained in that list. This solution seems to suit my needs and goes along with my principles of elegant programming.
But when it came down to MBean methods I was horrified. I use declarative security (an LDAP realm connected to Active Directory) in Glassfish. I couldnt find a suitable way to hire Filters or Listeners to do the job :). All I needed was security annotation, implementing method security. Well, I am extremely happy I
found your work, although its brazilian? commented :)
Posted by Stratos Pavlakis on January 08, 2008 at 02:55 AM PST #
Yes!
We are brazilians.
Now the project is in progress and we are integrating it better with declarative security and also in the near future will be possible to use the concept of config by exception, where you can override an annotation config with xml document.
Thanks for your feed-back and feel free to write me questions.
Regards,
Vinicius Senger
Posted by Vinicius Senger on January 22, 2008 at 04:01 PM PST #
Thanks for your feed-back, we are improving the project with new features.
We are from Sao Paulo, Brazil.
Feel free to contact us.
Posted by Vinicius Senger on January 31, 2008 at 08:02 AM PST #
i'm having trouble running the example on tomcat 6.0..
Posted by futch3 on February 03, 2008 at 02:16 PM PST #
Which kind of trouble / exception you are having?
How are you packing the war and which jar you have inside WEB-INF/lib?
Posted by Vinicius Senger on February 07, 2008 at 12:22 PM PST #
good
Posted by 59.162.117.235 on February 12, 2008 at 03:58 AM PST #
Hi Vinicius, I would like know how can I run this application with other container ou web server like TomCat or JBoss.
Thank you
Posted by Junior de Paula Sousa on February 15, 2008 at 10:41 AM PST #
how to give the link on command button and open the next page in the current page in jsf
Posted by roshan on February 15, 2008 at 11:30 PM PST #
Hi Vinicius,
These are good snippets, but I try it, and I don't understand, how I can set up my role mapping to users. The Myuser class isUserInRole() method allways return true, it means, that everybody has every role permissions?
Thank you for your answer!
Bye!
Csaba
Posted by Csaba on February 23, 2008 at 11:57 AM PST #
Great article. Exactly what I needed!
-Cameron McKenzie
http://www.scja.com
Posted by Cameron McKenzie on February 26, 2008 at 08:26 AM PST #
I would like to ask you to use the official discussion / support forum at facesannotations.dev.java.net.
Post the complete exception (if the case).
This library runs on Jboss 4.x, Tomcat 5.x and Glassfish.
Thanks,
Vinicius Senger
Posted by Vinicius Senger on March 24, 2008 at 06:53 AM PDT #
Is there a way to use these techniques to hide or gray out the button if the user doesn't have security?
Thanks,
--Erik Ostermueller
Posted by Erik Ostermueller on April 30, 2008 at 01:30 PM PDT #