Improving JSF Security Configuration With Secured Managed Beans

By Vinicius Senger

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".
  • Your Provider: Implement the 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:

  1. Add the following context parameter to the 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>
     
  2. Create your 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;
            }
          }  

  3. Provide page login with a navigation case called login:

           //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>

  4. Write a login managed bean that checks the user credentials and puts (or not) the user object into the HTTP session.

          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 a SessionUserProvider and has a very simple user and login page. To install and run the sample:
  1. Download the sample package and extract its contents. You should now see a newly extracted directory <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.

    Notice that the faces-config.xml file in the expanded sample package contains the declarations for the SecureActionListener and SecureNavigationHandler.

  2. Start the NetBeans IDE.

  3. Open the facesannotations-glassfish project as follows:

    • Select Open Project from the File menu.
    • Browse to the facesannotations-glassfish directory from the sample application download.
    • Click the Open Project Folder button.

  4. Run facesannotations-glassfish as follows:

    • Right click on the facesannotations-glassfish node in the Projects window.
    • Select Run Project.
    • Open your browser to the following URL:

      http://localhost:8080/facesannotations-glassfish/index.jsf

  5. You should see a page that contains two buttons: one button invokes an unprotected method. The other button invokes a protected method.
     
    31
     
    Click on both buttons and see what happens. You'll see that you can run the unprotected method, but the protected method requires you to have a special role.
     
    42
     

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.

Comments:

good to know, need more of the same , with more detail.

Posted by Lou Blocker on October 19, 2007 at 01:31 AM PDT #

All the source code is hosted at facesannotation.dev.java.net

Posted by Vinicius Senger on November 05, 2007 at 12: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 07, 2008 at 06:55 PM 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 08:01 AM 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 12:02 AM PST #

i'm having trouble running the example on tomcat 6.0..

Posted by futch3 on February 03, 2008 at 06:16 AM 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 04:22 AM PST #

good

Posted by guest on February 11, 2008 at 07:58 PM 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 02: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 03: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 03:57 AM PST #

Great article. Exactly what I needed!

-Cameron McKenzie

http://www.scja.com

Posted by Cameron McKenzie on February 26, 2008 at 12: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 23, 2008 at 11:53 PM 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 06:30 AM PDT #

Excellent article. Thanks.

Posted by Yuriy Semen on October 25, 2008 at 05:58 PM PDT #

Hi Vinicius,

Is there any way to intercept actionListeners ? Because the <action-listener> can only intercept actions.
<h:commandLink action="#{mb.action}" actionListener="#{mb.actionListener}" value="text"/>

In this exemple only mb.action is intercepted by the action-listener class.

Thanks

Posted by boutaounte faissal on June 20, 2009 at 06:24 AM PDT #

Hello Boutaounte,

We will be working on a mechanins to intercept anything but with JSF 2.0 only... If you need it in JSF 1.2 I can give you the directions or try to find some time to write this simple code to you.

Thanks a lot about your feed-back,
Vinicius Senger

Posted by Vinicius Senger on June 22, 2009 at 02:12 AM PDT #

Thank you very much Vinicius :)

I integrated this to the JSF core by adding some codes to "javax.faces.event.MethodExpressionActionListener.processAction(ActionEvent actionEvent)" but I would like to do it without changing any thing in this method.

I will be very happy to receive directions from you.

Thanks

Posted by boutaounte faissal on June 22, 2009 at 03:19 AM PDT #

Hi, everybody!

I can not download the archive with an example (http://java.sun.com/mailers/techtips/enterprise/2007/download/ttsept2007FacesSec.zip). Gives the following error: Page Not Fund. Who has this file (ttsept2007FacesSec.zip), send it to me, please, to my e-mail: devw08@gmail.com.

Thanks in advance!

Posted by devw08 on November 09, 2010 at 08:58 PM PST #

Post a Comment:
Comments are closed for this entry.
About

edort

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today