Sun Java Solaris Communities My SDN Account Join SDN
 
Enterprise Java Technologies Tech Tips

Using Type Substitution with Web Services, and Improving JSF Security Configuration with Secured Managed Beans

 

Welcome to the Enterprise Java Technologies Tech Tips for September 2007. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java Platform, Enterprise Edition (Java EE).

You can now read the Enterprise Java Technologies Tech Tips online as a web log.

This issue covers:

These tips were developed using an open source reference implementation of Java EE 5 called GlassFish, and the open source NetBeans IDE 5.5.1. You can download GlassFish from the GlassFish Community Downloads page. You can download the NetBeans IDE 5.5.1 from the NetBeans page.

You can download the sample archive for the tip Using Type Substitution With Web Services.

You can download the sample archive for the tip Improving JSF Security Configuration With Secured Managed Beans.

Any use of this code and/or information below is subject to the license terms.

Using Type Substitution with Web Services

By Doug Kohlert

Java Architecture for XML Binding (JAXB) 2.1 introduced a new annotation, @XmlSeeAlso, that you can use to make JAXB aware of additional types. Java API for XML-Based Web Services (JAX-WS) 2.1 also uses the @XmlSeeAlso annotation to allow use of abstract classes in a service endpoint interface (SEI). JAX-WS 2.1 allows you to specify the @XmlSeeAlso annotation on a SEI. JAX-WS reads this annotation at runtime making sure to pass all of the classes referenced by the annotation to JAXB via the JAXBContext. The use of the @XmlSeeAlso annotation in JAXB and JAX-WS enables support for type substitution, a subclassing concept that complements inheritance.

This tip will show you how to develop a simple web service that uses type substitution as well a client that consumes the web service. You'll see how to build the web service from a Java class and from a WSDL file.

A sample application accompanies this tip. The code examples in the tip are taken from the source code of the sample application.

Using Type Substitution in a Web Service

Suppose you want to build a web service that manages the inventory for a store that sells wakeboards and related equipment. Wakeboards are short boards made of buoyant material that are used to ride over the surface of a body of water, typically behind a boat or with a cable-skiing apparatus.

For simplicity, let's assume that the store sells only three types items: wakeboards, bindings, and towers for boats. You want the web service to be fairly simple to use and have a minimal amount of exposed operations. So to keep things simple, the web service uses an abstract Item class in its operations instead of using type-specific operations. The following Item class can be used to model any inventory object that you might want to expose through your web service:

   public abstract class Item implements Serializable {
       private long id;
       private String brand;
       private String name;
       private double price; 
       ...       
   }

Extending the Item class, you can define the following Wakeboard, WakeboardBinding and Tower classes:

   public class Wakeboard extends Item {
       private String size;       
   }
   
   public class WakeboardBinding extends Item {
       private String size;       
   }

   public class Tower extends Item {
       private Fit fit;    
       private String tubing;
    
       public static enum Fit { Custom, Exact, Universal };       
   }

Because this example is about type substitution, let's make the inheritance hierarchy a little more interesting by introducing a Wearable abstract class. Wearable holds the size attribute for both the Wakeboard and WakeboardBinding classes. The Wearable class is defined as follows:

   public abstract class Wearable extends Item { 
       protected String size;       
   }

And the resulting Wakeboard and WakeboardBinding classes are:

   public class Wakeboard extends Wearable {   
   }

   public class WakeboardBinding extends Wearable {   
   }

Also, because the web service manages inventory, you'll want the inventory items to be persisted to a database using the Java Persistence API (sometimes referred to as JPA). To do this, you need to add an @Entity annotation to each of the classes that will be persisted. The only class that you probably don't want to persist is the Wearable class. You can add the @MappedSuperclass annotation to this class so that the JPA will use the attributes of this class for persisting subclasses. Next, you need to add the @Id and the @GeneratedValue(strategy = GenerationType.AUTO) annotations to the Item.Id field. As a result, the field will be used as the primary key in the database and the Id will be automatically generated if not provided. Finally, because you might add new types of Items into the system at a later time, you should add the @Inheritance(strategy=InheritanceType.JOINED) annotation to the Item class. This will store each subclass in its own database table.

The final data classes look like the following:

   @Entity
   @Inheritance(strategy=InheritanceType.JOINED)
   public abstract class Item implements Serializable {
       @Id
       @GeneratedValue(strategy = GenerationType.AUTO)
       private Long id;
       private String brand;
       private String itemName;
       private double price;

       // Getters & setters
       ...       
   }

   @MappedSuperclass
   public abstract class Wearable extends Item { 
       protected String size;
       ...
   }

   @Entity
   public class Wakeboard extends Wearable {}

   @Entity
   public class WakeboardBinding extends Wearable {}

   @Entity
   public class Tower extends Item {
       private Fit fit;    
       private String tubing;
    
       public static enum Fit { Custom, Exact, Universal };
       ...       
   }

Now that you defined the data model for the application, you can now define the web service interface. Because the application manages information about wakeboard equipment, let's call the web service WakeRider and let's expose four operations in the web service: addItem, updateItem, removeItem, and getItems.

Here is what the WakerRider class looks like:

   @WebService()
   public class WakeRider {
       ...
       public List<Item> getItems() {...}
  
       public boolean addItem(Item item) {...}
    
       public boolean updateItem(Item item) {...}

       public boolean removeItem(Item item) {...}
   }

If you deployed this web service and then looked at the generated WSDL and schema, you would notice that only the Item type is defined -- there is no mention of Wearable, Wakeboard, WakeboardBinding, or Tower. This is because when JAX-WS introspects the WakeRider class there is no mention of the other classes. To remedy that you can use the new @XmlSeeAlso annotation and list the other classes that you want to expose through the WakeRider web service.

Here is what the WakeRider class looks like with the @XmlSeeAlso annotation:

   @WebService()
   @XmlSeeAlso({Wakeboard.class, 
                WakeboardBinding.class, 
                Tower.class})   
   public class WakeRider {
       ...
   }

Now when you deploy the WakeRider service and look at the generated schema, you will see types for Item, Wearable, Wakeboard, WakeboardBinding, and Tower as well as some other types used internally by JAX-WS and JAXB.

Starting From WSDL

You can use type substitution in a web service that is built from a WSDL file. What's particularly nice about this is that using type substitution when starting from WSDL is totally transparent. When you import a WSDL file with JAX-WS 2.1, the generated proxy class is required to have the appropriate @XmlSeeAlso annotation. For example, the imported WakeRider proxy from the web service example in the previous section would have an @XmlSeeAlso annotation like the following:

   @WebService(name="WakeRider",            
               targetNamespace="http://wakerider/")
   @XmlSeeAlso({ObjectFactory.class})
   public interface WakeRider {
       ...
   }

Notice that the @XmlSeeAlso annotation in the proxy contains the ObjectFactory.class instead of listing the classes. The ObjectFactory class is a JAXB required class that provides information about all of the Java types that JAXB needs to be aware of in the given package. In this example, the ObjectFactory class will have references to the Item, Wearable, Wakeboard, WakeboardBinding and Tower classes. There is nothing that you need to do to enable type substitution when starting from WSDL.

The WakeRider Client

Invoking the WakeRider web service from a client is the same as invoking any other web service using JAX-WS. All you need to do is get a WakeRider proxy from the generated WakeRider web service and invoke the operations on the proxy. The sample application that accompanies this tip contains a NetBeans 5.5.1 project for a Java Platform, Standard Edition (Java SE) application named wrmanager. You can use the application to add, remove, or edit items in the WakeRider web service inventory.

There is also a NetBeans 5.5.1 project for a JavaServer Faces (JSF) technology application named wrviewer. The application uses the WakeRider web service to view the current inventory.

Both of these client applications contain code similar to the following for invoking an operation on the WakeRider web service:

   WakeRiderService service = new WakeRiderService();
   port = service.getWakeRiderPort();
   List<Item> items = port.getItems();
   for (Item item : items) {
       if (item instanceof Wakeboard) {
           ...
       } else if (Item instance of WakeboardBinding) {
        ...
       } else if (Item instance of Tower) {
           ...
       }
   }

Running the Sample Code

The sample code for this tip is available as three NetBeans projects:

  • wrservice. Defines the WakeRider endpoint.
  • wrviewer. A JSF page for viewing the WakeRider inventory.
  • wrmanager. A Java SE application for adding, removing, and editing items in the WakeRider.

You can build and run the sample code using the NetBeans 5.5.1 IDE as follows:

  1. If you haven't already done so, download and install the NetBeans 5.5.1 IDE.

  2. If you haven't already done so, download and install GlassFish V2 RC 4 or later.

  3. Download the sample application for the tip and extract its contents. You should now see the newly extracted directory as <sample_install_dir>/wakerider, where <sample_install_dir> is the directory where you installed the sample application. For example, if you extracted the contents to C:\ on a Windows machine, then your newly created directory should be at C:\wakerider. The wakerider directory contains one directory for each of the NetBeans projects: wrservice, wrviewer, and wrmanager.

  4. Start the NetBeans IDE. Run Netbeans with JDK 5.0. You can also use JDK 6, however in that case, you will also need to follow the instructions in Running on top of JDK 6.

  5. Add GlassFish V2 to the NetBeans Application Servers as follows:

    • Right click on Servers node in the Runtime window.
    • Select Add Server.
    • Leave the Server as Sun Java System Application Server.
    • Click the Next button.
    • Click the Browse button and browse to the location that you installed GlassFish V2.
    • Click the Choose button.
    • Click the Next button.
    • Set the Admin Password to the default, adminadmin, unless you chose a different password for GlassFish.
    • Click the Finish button.

  6. Open the wrservice project as follows:

    • Select Open Project from the File menu.
    • Browse to the wrservice directory from the sample application download.
    • Click the Open Project Folder button.
    • If you are alerted to a "Missing Server Problem", resolve it by right clicking on the wrservice node in the Projects window and selecting Resolve Missing Server Problem. Then select Sun Java System Application Server.

  7. Deploy the wrservice project as follows:

    • Right click the wrservice node in the Projects window.
    • Select Deploy Project.

  8. Open the wrviewer project as follows:

    • Select Open Project from the File menu.
    • Browse to the wrviewer directory from the sample application download.
    • Click the Open Project Folder button.
    • You may need to resolve a missing server problem as described in step 6.

  9. Run wrviewer as follows:

    • Right click on the wrviewer node in the Projects window.
    • Select Run Project. This should open a window in your web browser that displays the current WakeRider inventory. The inventory should be empty the first time you run wrviewer.

    Wake Rider Inventory
    WakeRider Inventory
     
  10. Open the wrmanager project as follows:

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

  11. Run wrmanager as follows:

    • Right client on the wrmanager node in the Projects window.
    • Select Run Project. This should open the WakeRider Inventory Manager application.

    WakeRider Inventory Manager
    WakeRider Inventory Manager
     
  12. Add, delete, edit, or view inventory items as follows:
  • To add an item, click the Add button in the WakeRider Inventory Manager application, fill in the Add Item dialog and click the OK button.

  • To edit an item, select the item in the appropriate inventory window in the WakeRider Inventory Manager application and click the Edit button. Modify the contents of the Edit Item dialog and click the OK button.

  • To delete an item, select the item in the appropriate inventory window in WakeRider Inventory Manager application and click the Remove button.

  • To view current inventory items in the wrmanager application, view or refresh the wrviewer page in your browser.

  • WakeRider Inventory
    WakeRider Inventory
     

About the Author

Doug Kohlert is a senior staff engineer in the Web Technologies and Standards division of Sun Microsystems where he is the specification lead for JAX-WS.

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.

Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.