您的位置:首页 > Web前端 > JavaScript

Improving JSF Security Configuration With Secured Managed Beans

2007-10-24 23:37 537 查看
太晚了,有空再看,明天还要上课...
http://blogs.sun.com/enterprisetechtips/entry/improving_jsf_security_configuration_with

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
: Int
4000
egrate 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
:

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>

 


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;

        }

      }  


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>


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

Start the NetBeans IDE.

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.

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


You should see a page that contains two buttons: one button invokes an unprotected method. The other button invokes a protected method.
 


 
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.
 


 

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.

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息