Sun | Monday, October 1, 2007

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.

Join the discussion

Comments ( 19 )
  • Lou Blocker Friday, October 19, 2007

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


  • Vinicius Senger Monday, November 5, 2007

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


  • Stratos Pavlakis Tuesday, January 8, 2008

    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 :)


  • Vinicius Senger Tuesday, January 22, 2008

    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


  • Vinicius Senger Thursday, January 31, 2008

    Thanks for your feed-back, we are improving the project with new features.

    We are from Sao Paulo, Brazil.

    Feel free to contact us.


  • futch3 Sunday, February 3, 2008

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


  • Vinicius Senger Thursday, February 7, 2008

    Which kind of trouble / exception you are having?

    How are you packing the war and which jar you have inside WEB-INF/lib?


  • guest Tuesday, February 12, 2008

    good


  • Junior de Paula Sousa Friday, February 15, 2008

    Hi Vinicius, I would like know how can I run this application with other container ou web server like TomCat or JBoss.

    Thank you


  • roshan Friday, February 15, 2008

    how to give the link on command button and open the next page in the current page in jsf


  • Csaba Saturday, February 23, 2008

    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


  • Cameron McKenzie Tuesday, February 26, 2008

    Great article. Exactly what I needed!

    -Cameron McKenzie

    http://www.scja.com


  • Vinicius Senger Monday, March 24, 2008

    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


  • Erik Ostermueller Wednesday, April 30, 2008

    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


  • Yuriy Semen Sunday, October 26, 2008

    Excellent article. Thanks.


  • boutaounte faissal Saturday, June 20, 2009

    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


  • Vinicius Senger Monday, June 22, 2009

    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


  • boutaounte faissal Monday, June 22, 2009

    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


  • devw08 Wednesday, November 10, 2010

    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!


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
 

Visit the Oracle Blog

 

Contact Us

Oracle

Integrated Cloud Applications & Platform Services