Managing Configuration Information

   

Whenever your application interfaces with external services, you need to specify configuration parameters: URLs, usernames, passwords, and so on. You should never hardcode these parameters inside your application classes doing so would make it difficult to update passwords, switch to alternative servers, and so on.

In the section on database services, you saw a reasonable approach for managing the database configuration. The configuration information is placed inside server.xml. The servlet container uses this information to construct a data source and bind it to a well-known name. The classes that need to access the database use JNDI look up the data source.

Placing configuration information into server.xml is appropriate for a global resource such as a database. This resource can be used by all web applications inside the container. On the other hand, application-specific configuration information should be placed inside web.xml or faces-config.xml. Using the example of an LDAP connection, we explore all three possibilities.

Configuring a Bean

Whenever you define a bean in faces-config.xml, you can provide initialization parameters by using the managed-property element. Here is how we can initialize a bean that connects to an LDAP directory:

 

 <managed-bean>    <managed-bean-name>userdir</managed-bean-name>    <managed-bean-class>com.corejsf.UserDirectoryBean</managed-bean-class>    <managed-bean-scope>application</managed-bean-scope>    <managed-property>       <property-name>URL</property-name>       <value>ldap://localhost:389</value>    </managed-property>    <managed-property>       <property-name>managerDN</property-name>       <value>cn=Manager,dc=corejsf,dc=com</value>    </managed-property>    <managed-property>       <property-name>managerPassword</property-name>       <value>secret</value>    </managed-property> </managed-bean> 

You see the familiar managed-bean-name and managed-bean-class elements. However, this bean is given application scope. The bean object stays alive for the duration of the entire application, and it can serve multiple sessions. Finally, we used the managed-property settings to initialize the bean. Thus, we achieved our goal of placing these initialization parameters inside a configuration file rather than hardwiring them into the bean code.

Of course, our bean needs setters for these properties:

 

 public class UserDirectoryBean {    private String url;    private String managerDN;    private String managerPW;    public void setManagerDN(String newValue) { managerDN = newValue; }    public void setManagerPassword(String newValue) { managerPW = newValue; }    public void setURL(String newValue) { url = newValue; }    public DirContext getRootContext() throws NamingException { ... } } 

When the bean is constructed, the setters are invoked with the values specified in faces-config.xml.

Finally, client code needs to have access to the bean object. For example, suppose the UserBean class wants to connect to the directory:

 

 UserDirectoryBean userdir = ... // how? DirContext context = userdir.connect(dn, pw); 

To look up a JSF bean, you use its value binding of its name, as in the following statements:

 

 FacesContext context = FacesContext.getCurrentInstance(); Application app = context.getApplication(); ValueBinding binding = app.createValueBinding("#{userdir}"); UserDirectoryBean dir = (UserDirectoryBean) binding.getValue(context); 

In summary, here are the steps for configuring a JSF bean:

  1. Place the configuration parameters inside managed-property elements in the faces-config.xml file.

  2. Provide property setters for these properties in the bean class.

  3. Look up the bean object through its value binding.

This configuration method is straightforward and convenient. However, it is not suitable for configuring objects that should be available to multiple web applications. Moreover, purists might argue that faces-config.xml is intended to describe the logic of a web application, not its interface with external resources, and that web.xml would be more appropriate for the latter. Read on if either of these objections matters to you.

Configuring the External Context

In this section, we assume that your JSF application is launched as a servlet. You can supply parameters in web.xml by providing a set of context-param elements inside the web-app element:

 

 <web-app>    <context-param>       <param-name>URL</param-name>       <param-value>ldap://localhost:389</param-value>    </context-param>    <context-param>       <param-name>managerDN</param-name>       <param-value>cn=Manager,dc=corejsf,dc=com</param-value>    </context-param>    <context-param>       <param-name>managerPassword</param-name>       <param-value>secret</param-value>    </context-param>    ... </web-app> 

To read a parameter, get the external context object. That object describes the execution environment that launched your JSF application. If you use a servlet container, then the external context is a wrapper around the ServletContext object. The ExternalContext class has a number of convenience methods to access properties of the underlying servlet context. The getInitParameter method retrieves a context parameter value with a given name.

CAUTION

graphics/caution_icon.gif

Do not confuse context-param with init-param. The latter tag is used for parameters that a servlet can process at startup. It is unfortunate that the method for reading a context parameter is called getInitParameter.


Here is the code for getting an LDAP context from configuration parameters in web.xml:

 

 public DirContext getRootContext() throws NamingException {    ExternalContext external       = FacesContext.getCurrentInstance().getExternalContext();    String managerDN = external.getInitParameter("managerDN");    String managerPW = external.getInitParameter("managerPassword");    String url = external.getInitParameter("URL");    Hashtable env = new Hashtable();    env.put(Context.SECURITY_PRINCIPAL, managerDN);    env.put(Context.SECURITY_CREDENTIALS, managerPW);    DirContext initial = new InitialDirContext(env);    Object obj = initial.lookup(url);    if (!(obj instanceof DirContext))       throw new NamingException("No directory context");    return (DirContext) obj; } 

Follow these steps for accessing resources through the external context:

  1. Place the configuration parameters inside context-param elements in the web.xml file.

  2. Use the ExternalContext to look up the parameter values.

  3. Turn the parameters into objects for your application.

As you can see, this configuration method works at a lower level than the configuration of a JSF bean. The web.xml file simply contains an unstructured list of parameters. It is up to you to construct objects that make use of these parameters.

 

graphics/api_icon.gif

 

 javax.faces.context.FacesContext 

  • ExternalContext getExternalContext()

    Gets the external context, a wrapper such as a servlet or portlet context around the execution environment of this JSF application.

 

graphics/api_icon.gif

 

 javax.faces.context.ExternalContext 

  • String getInitParameter(String name)

    Gets the initialization parameter with the given name.

Configuring a Container-Managed Resource

We now discuss how to specify container-wide resources. The information in this section is specific to Tomcat. Other containers will have similar mechanisms, but the details will differ.

Earlier in this chapter, we showed you how to configure a JDBC data source by specifying the database URL and login parameters in Tomcat's server.xml file. We simply used a JNDI lookup to obtain the data source object. This is an attractive method for specifying systemwide resources. Fortunately, Tomcat lets you fit your own resources into the same mechanism.

As with JDBC data sources, you specify a Resource and its ResourceParams in server.xml. For example, here is the configuration information for an LDAP directory.

 

 <Resource name="ldap/mydir" auth="Container"    type="javax.naming.directory.DirContext"/> <ResourceParams name="ldap/mydir">    <parameter>       <name>factory</name>       <value>com.corejsf.DirContextFactory</value>    </parameter>    <parameter>       <name>URL</name>       <value>ldap://localhost:389</value>    </parameter>    <parameter>       <name>java.naming.security.principal</name>       <value>cn=Manager,dc=corejsf,dc=com</value>    </parameter>    <parameter>       <name>java.naming.security.credentials</name>       <value>secret</value>    </parameter> </ResourceParams> 

However, Tomcat has no standard "factory" for LDAP directories. This class uses the custom factory com.corejsf.DirContextFactory. All factories need to implement the ObjectFactory interface type and implement the getObjectInstance method.

 

 public class DirContextFactory implements ObjectFactory {    public Object getObjectInstance(Object obj,       Name n, Context nameCtx, Hashtable environment)       throws NamingException {       ...    } } 

This method, defined in glorious generality, can be used to produce any object from arbitrary configuration information. There is quite a bit of variability in how the parameters are used, but fortunately we only need to understand what parameters Tomcat supplies when requesting a resource. Tomcat places the configuration parameters into a Reference object, a kind of hash table on a megadose of steroids. Our factory simply places the parameters into a plain hash table and then gets the directory context see Listing 10-8 for the complete source code.

NOTE

graphics/note_icon.gif

The class com.sun.jndi.ldap.LdapCtxFactory (which is explicitly invoked in Sun's JNDI tutorial) also implements the ObjectFactory interface. Could you use that class as a factory for LDAP connections in Tomcat's server.xml file? Sadly, the answer is no. The getObjectInstance method of com.sun.jndi.ldap.LdapCtxFactory expects an Object parameter that is either an URL string, an array of URL strings, or a Reference object containing values with key "URL". The other environment settings must be provided in the Hashtable parameter. That's not what Tomcat supplies.


Note that we simply use the standard JNDI environment names for the principal and credentials in the server.xml file. The Context interface constants that we used previously are merely shortcuts for the environment names. For example, Context.SECURITY_PRINCIPAL is the string "java.naming.security.principal". (Admittedly, the constant names aren't much shorter, but they are safer. If you misspell the constant, then the compiler will warn you. If you misspell the environment name, your application will mysteriously fail.)

Now that you have completed the configuration, the remainder is smooth sailing. Your program simply accesses the resource through its JNDI name:

 

 public DirContext getRootContext() throws NamingException {    Context ctx = new InitialContext();    return (DirContext) ctx.lookup("java:comp/env/ldap/mydir"); } 

In summary, here are the steps for configuring a container-wide resource:

  1. Place the configuration parameters inside the ResourceParams section that has the same name as the Resource element for your resource.

  2. If you use a custom resource factory, deploy the class so that the container can load it (for example, in a JAR file that you place inside the common/lib directory).

  3. Look up the resource object through its JNDI name.

Listing 10-8. ldap3/misc/com/corejsf/DirContextFactory.java
  1. package com.corejsf;  2.  3. import java.util.Enumeration;  4. import java.util.Hashtable;  5. import javax.naming.Context;  6. import javax.naming.Name;  7. import javax.naming.NamingException;  8. import javax.naming.RefAddr;  9. import javax.naming.Reference; 10. import javax.naming.directory.DirContext; 11. import javax.naming.directory.InitialDirContext; 12. import javax.naming.spi.ObjectFactory; 13. 14. public class DirContextFactory implements ObjectFactory { 15.    public Object getObjectInstance(Object obj, 16.       Name n, Context nameCtx, Hashtable environment) 17.       throws NamingException { 18. 19.       Hashtable env = new Hashtable(); 20.       String url = null; 21.       Reference ref = (Reference) obj; 22.       Enumeration addrs = ref.getAll(); 23.       while (addrs.hasMoreElements()) { 24.           RefAddr addr = (RefAddr) addrs.nextElement(); 25.           String name = addr.getType(); 26.           String value = (String) addr.getContent(); 27.           if (name.equals("URL")) url = value; 28.           else env.put(name, value); 29.       } 30.       DirContext initial = new InitialDirContext(env); 31.       if (url == null) return initial; 32.       else return initial.lookup(url); 33.    } 34. } 

NOTE

graphics/note_icon.gif

Compile this file, place it inside a JAR file, and put the JAR file into the common/lib directory of Tomcat.

 

 cd corejsf-examples/ch10/ldap3/misc javac com/corejsf/DirContextFactory.java jar cvf tomcat/common/lib/dirctxfactory.jar com/corejsf/*.class 

Remember to restart the server.


Creating an LDAP Application

We now put together a complete application that stores user information in an LDAP directory.

The application simulates a news web site that gives users free access to news as long as they provide some information about themselves. We do not actually provide any news. We simply provide a screen to log in (Figure 10-9) and a separate screen to register for the service (Figure 10-10). Upon successful login, users can read news and update their personal information (Figure 10-11).

Figure 10-9. Logging In to the News Service

graphics/10fig09.jpg


Figure 10-10. Registering for the News Service

graphics/10fig10.jpg


Figure 10-11. Main Screen of the News Service

graphics/10fig11.jpg


The update screen is similar to the registration screen, and we do not show it. Figure 10-12 shows the directory structure, and Figure 10-13 shows the page flow between the news service pages.

Figure 10-12. The Directory Structure of the LDAP Example

graphics/10fig12.jpg


Figure 10-13. Page Flow of the News Service

graphics/10fig13.jpg


We provide three versions of this application, with configuration information in faces-config.xml, web.xml, and server.xml, respectively.

All three versions have identical web pages see Listings 10-9 through 10-12. (We omit the listings of repetitive pages.) The primary difference between the versions is the implementation of the getRootContext method in the UserBean class (Listing 10-13). The first application has a UserDirectoryBean class (Listing 10-14) that is configured in faces-config.xml (Listing 10-15). The second application makes an ad hoc lookup of servlet initialization parameters. The third version makes a JNDI lookup, using the class of Listing 10-8. See the preceding sections for details. Finally, for completeness, Listing 10-16 contains the code for the Name class that is used in the UserBean class.

Listing 10-9. ldap/index.jsp
 1. <html> 2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> 4.    <f:view> 5.       <head> 6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/> 7.          <title><h:outputText value="#{msgs.title}"/></title> 8.       </head> 9.       <body> 10.          <h:form> 11.             <h1><h:outputText value="#{msgs.enterNameAndPassword}"/></h1> 12.             <h:panelGrid columns="2"> 13.                <h:outputText value="#{msgs.loginID}"/> 14.                <h:inputText value="#{user.id}"/> 15. 16.                <h:outputText value="#{msgs.password}"/> 17.                <h:inputSecret value="#{user.password}"/> 18.             </h:panelGrid> 19.             <h:commandButton value="#{msgs.login}" action="#{user.login}"/> 20.             <br/> 21.             <h:outputText value="#{msgs.signupNow}"/> 22.             <h:commandButton value="#{msgs.signup}" action="signup"/> 23.          </h:form> 24.       </body> 25.    </f:view> 26. </html> 

Listing 10-10. ldap/signup.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.          <title><h:outputText value="#{msgs.title}"/></title>  8.       </head>  9.       <body> 10.          <h:form> 11.             <h1><h:outputText value="#{msgs.newUserSignup}"/></h1> 12.             <p><h:outputText value="#{msgs.newUserSignup_detail}"/></p> 13.             <h:panelGrid columns="2"> 14.                <h:outputText value="#{msgs.firstName}"/> 15.                <h:inputText value="#{user.name.first}"/> 16. 17.                <h:outputText value="#{msgs.middleInitial}"/> 18.                <h:inputText value="#{user.name.middle}"/> 19. 20.                <h:outputText value="#{msgs.lastName}"/> 21.                <h:inputText value="#{user.name.last}"/> 22. 23.                <h:outputText value="#{msgs.email}"/> 24.                <h:inputText value="#{user.email}"/> 25. 26.                <h:outputText value="#{msgs.loginID}"/> 27.                <h:inputText value="#{user.id}"/> 28. 29.                <h:outputText value="#{msgs.password}"/> 30.                <h:inputSecret value="#{user.password}"/> 31.             </h:panelGrid> 32.             <h:commandButton value="#{msgs.submit}" action="#{user.signup}"/> 33.             <h:commandButton value="#{msgs.cancel}" action="signup_cancel"/> 34.          </h:form> 35.       </body> 36.    </f:view> 37. </html> 

Listing 10-11. ldap/welcome.jsp
  1.<html>  2.   <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.   <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.   <f:view>  5.      <head>  6.         <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.         <title><h:outputText value="#{msgs.title}"/></title>  8.      </head>  9.      <body> 10.         <h:form> 11.            <h1><h:outputText value="#{msgs.success}"/></h1> 12.            <p> 13.               <h:outputText value="#{msgs.welcome}"/> 14.               <h:outputText value="#{user.name}"/>! 15.            </p> 16.            <p> 17.               <h:outputText value="#{msgs.success_detail}"/> 18.            </p> 19.            <h:commandButton value="#{msgs.update}" action="update"/> 20.            <h:commandButton value="#{msgs.logout}" action="#{user.logout}"/> 21.         </h:form> 22.      </body> 23.   </f:view> 24.</html> 

Listing 10-12. ldap/loginError.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.          <title><h:outputText value="#{msgs.title}"/></title>  8.       </head>  9.       <body> 10.          <h:form> 11.             <h1><h:outputText value="#{msgs.loginError}"/></h1> 12.             <p> 13.                <h:outputText value="#{msgs.loginError_detail}"/> 14.             </p> 15.             <p> 16.                <h:commandButton value="#{msgs.tryAgain}" action="login"/> 17.                <h:commandButton value="#{msgs.signup}" action="signup"/> 18.             </p> 19.          </h:form> 20.       </body> 21.    </f:view> 22. </html> 

Listing 10-13. ldap/WEB-INF/classes/com/corejsf/UserBean.java
  1. package com.corejsf;  2.  3. import java.util.logging.Level;  4. import java.util.logging.Logger;  5. import javax.faces.application.Application;  6. import javax.faces.context.FacesContext;  7. import javax.faces.el.ValueBinding;  8. import javax.naming.NameNotFoundException;  9. import javax.naming.NamingException;  10. import javax.naming.directory.Attributes;  11. import javax.naming.directory.BasicAttributes;  12. import javax.naming.directory.DirContext;  13.  14. public class UserBean {  15.    private Name name;  16.    private String id;  17.    private String email;  18.    private String password;  19.    private Logger logger = Logger.getLogger("com.corejava");  20.  21.    public UserBean() { name = new Name(); }  22.  23.    public DirContext getRootContext() throws NamingException {  24.       FacesContext context = FacesContext.getCurrentInstance();  25.       Application app = context.getApplication();  26.       ValueBinding binding = app.createValueBinding("#{userdir}");  27.       UserDirectoryBean dir =  28.          (UserDirectoryBean) binding.getValue(context);  29.       return dir.getRootContext();  30.   }  31.  32.   public Name getName() { return name; }  33.   public void setName(Name newValue) { name = newValue; }  34.  35.   public String getEmail() { return email; }  36.   public void setEmail(String newValue) { email = newValue; }  37.  38.   public String getId() { return id; }  39.   public void setId(String newValue) { id = newValue; }  40.  41.   public String getPassword() { return password; }  42.   public void setPassword(String newValue) { password = newValue; }  43.  44.   public String login() {  45.      try {  46.         DirContext context = getRootContext();  47.         try {  48.            String dn = "u,ou=people,dc=corejsf,dc=com";  49.            Attributes userAttributes = context.getAttributes(dn);  50.            String cn = (String) userAttributes.get("cn").get();  51.            name.parse(cn);  52.            email = (String) userAttributes.get("mail").get();  53.            byte[] pw = (byte[])  54.               userAttributes.get("userPassword").get();  55.            if (password.equals(new String(pw)))  56.               return "login_success";  57.            else  58.               return "login_failure";  59.         } finally {  60.            context.close();  61.         }  62.      }  63.      catch (NamingException ex) {  64.         logger.log(Level.SEVERE, "loginAction", ex);  65.         return "login_error";  66.      }  67.   }  68.  69.   public String signup() {  70.      try {  71.         DirContext context = getRootContext();  72.         try {  73.            String dn = "u,ou=people,dc=corejsf,dc=com";  74.  75.            try {  76.                context.lookup(dn);  77.                return "signup_failure";  78.             }  79.             catch (NameNotFoundException ex) {}  80.  81.             Attributes attrs = new BasicAttributes();  82.             attrs.put("objectClass", "inetOrgPerson");  83.             attrs.put("uid", id);  84.             attrs.put("sn", name.getLast());  85.             attrs.put("cn", name.toString());  86.             attrs.put("mail", email);  87.             attrs.put("userPassword", password.getBytes());  88.             context.createSubcontext(dn, attrs);  89.          } finally {  90.             context.close();  91.          }  92.       }  93.       catch (NamingException ex) {  94.          logger.log(Level.SEVERE, "loginAction", ex);  95.          return "signup_error";  96.       }  97.  98.       return "signup_success";  99.    } 100. 101.    public String update() { 102.       try { 103.          DirContext context = getRootContext(); 104.          try { 105.             String dn = "u,ou=people,dc=corejsf,dc=com"; 106.             Attributes attrs = new BasicAttributes(); 107.             attrs.put("sn", name.getLast()); 108.             attrs.put("cn", name.toString()); 109.             attrs.put("mail", email); 110.             attrs.put("userPassword", password.getBytes()); 111.             context.modifyAttributes(dn, 112.                DirContext.REPLACE_ATTRIBUTE, attrs); 113.          } finally { 114.             context.close(); 115.          } 116.       } 117.       catch (NamingException ex) { 118.          logger.log(Level.SEVERE, "updateAction", ex); 119.          return "internal_error"; 120.       } 121. 122.       return "update_success"; 123.    } 124. 125.    public String logout() { 126.       password = ""; 127.       return "logout_success"; 128.    } 129. } 

Listing 10-14. ldap/WEB-INF/classes/com/corejsf/UserDirectoryBean.java
  1. package com.corejsf;  2.  3. import java.util.Hashtable;  4. import javax.naming.Context;  5. import javax.naming.NamingException;  6. import javax.naming.directory.DirContext;  7. import javax.naming.directory.InitialDirContext;  8.  9. public class UserDirectoryBean { 10.    private String url; 11.    private String managerDN; 12.    private String managerPW; 13. 14.    public void setManagerDN(String newValue) { managerDN = newValue; } 15.    public void setManagerPassword(String newValue) { 16.       managerPW = newValue; } 17.    public void setURL(String newValue) { url = newValue; } 18. 19.    public DirContext getRootContext() throws NamingException { 20.       Hashtable env = new Hashtable(); 21.       env.put(Context.SECURITY_PRINCIPAL, managerDN); 22.       env.put(Context.SECURITY_CREDENTIALS, managerPW); 23.       DirContext initial = new InitialDirContext(env); 24. 25.       Object obj = initial.lookup(url); 26.       if (!(obj instanceof DirContext)) 27.          throw new NamingException("No directory context"); 28.       return (DirContext) obj; 29.    } 30. } 

Listing 10-15. ldap/WEB-INF/faces-config.xml
  1. <?xml version="1.0"?>  2.  3. <!DOCTYPE faces-config PUBLIC  4.   "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"  5.   "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">  6.  7. <faces-config>  8.  9.    <navigation-rule>  10.       <from-view-id>/index.jsp</from-view-id>  11.       <navigation-case>  12.          <from-outcome>login_success</from-outcome>  13.          <to-view-id>/welcome.jsp</to-view-id>  14.       </navigation-case>  15.       <navigation-case>  16.          <from-outcome>login_error</from-outcome>  17.          <to-view-id>/loginError.jsp</to-view-id>  18.       </navigation-case>  19.       <navigation-case>  20.          <from-outcome>login_failure</from-outcome>  21.          <to-view-id>/loginError.jsp</to-view-id>  22.       </navigation-case>  23.       <navigation-case>  24.          <from-outcome>signup</from-outcome>  25.          <to-view-id>/signup.jsp</to-view-id>  26.       </navigation-case>  27.    </navigation-rule>  28.    <navigation-rule>  29.       <from-view-id>/signup.jsp</from-view-id>  30.       <navigation-case>  31.          <from-outcome>signup_success</from-outcome>  32.          <to-view-id>/welcome.jsp</to-view-id>  33.       </navigation-case>  34.       <navigation-case>  35.          <from-outcome>signup_failure</from-outcome>  36.          <to-view-id>/signupError.jsp</to-view-id>  37.       </navigation-case>  38.       <navigation-case>  39.          <from-outcome>signup_error</from-outcome>  40.          <to-view-id>/signupError.jsp</to-view-id>  41.       </navigation-case>  42.       <navigation-case>  43.          <from-outcome>signup_cancel</from-outcome>  44.          <to-view-id>/index.jsp</to-view-id>  45.       </navigation-case>  46.    </navigation-rule>  47.    <navigation-rule>  48.       <from-view-id>/welcome.jsp</from-view-id>  49.       <navigation-case>  50.          <from-outcome>update</from-outcome>  51.          <to-view-id>/update.jsp</to-view-id>  52.       </navigation-case>  53.       <navigation-case>  54.          <from-outcome>logout_success</from-outcome>  55.          <to-view-id>/index.jsp</to-view-id>  56.       </navigation-case>  57.    </navigation-rule>  58.    <navigation-rule>  59.       <from-view-id>/update.jsp</from-view-id>  60.       <navigation-case>  61.          <from-outcome>update_success</from-outcome>  62.          <to-view-id>/welcome.jsp</to-view-id>  63.       </navigation-case>  64.       <navigation-case>  65.          <from-outcome>update_cancel</from-outcome>  66.          <to-view-id>/welcome.jsp</to-view-id>  67.       </navigation-case>  68.    </navigation-rule>  69.    <navigation-rule>  70.       <navigation-case>  71.          <from-outcome>login</from-outcome>  72.          <to-view-id>/index.jsp</to-view-id>  73.       </navigation-case>  74.       <navigation-case>  75.          <from-outcome>internal_error</from-outcome>  76.          <to-view-id>/internalError.jsp</to-view-id>  77.       </navigation-case>  78.    </navigation-rule>  79.  80.    <managed-bean>  81.       <managed-bean-name>user</managed-bean-name>  82.       <managed-bean-class>com.corejsf.UserBean</managed-bean-class>  83.       <managed-bean-scope>session</managed-bean-scope>  84.    </managed-bean>  85.  86.    <managed-bean>  87.       <managed-bean-name>userdir</managed-bean-name>  88.       <managed-bean-class>com.corejsf.UserDirectoryBean</managed-bean-class>  89.       <managed-bean-scope>application</managed-bean-scope>  90.       <managed-property>  91.          <property-name>URL</property-name>  92.          <value>ldap://localhost:389</value>  93.       </managed-property>  94.       <managed-property>  95.          <property-name>managerDN</property-name>  96.          <value>cn=Manager,dc=corejsf,dc=com</value>  97.       </managed-property>  98.       <managed-property>  99.          <property-name>managerPassword</property-name> 100.          <value>secret</value> 101.       </managed-property> 102.    </managed-bean> 103. 104. </faces-config> 

Listing 10-16. ldap/WEB-INF/classes/com/corejsf/Name.java
  1. package com.corejsf;  2.  3. public class Name {  4.    private String first;  5.    private String middle;  6.    private String last;  7.  8.    public Name() { first = ""; middle = ""; last = ""; }  9. 10.    public String getFirst() { return first; } 11.    public void setFirst(String newValue) { first = newValue; } 12. 13.    public String getMiddle() { return middle; } 14.    public void setMiddle(String newValue) { middle = newValue; } 15. 16.    public String getLast() { return last; } 17.    public void setLast(String newValue) { last = newValue; } 18. 19.    public void parse(String fullName) { 20.       int firstSpace = fullName.indexOf(' '); 21.       int lastSpace = fullName.lastIndexOf(' '); 22.       if (firstSpace == -1) { 23.          first = ""; 24.          middle = ""; 25.          last = fullName; 26.       } 27.       else { 28.          first = fullName.substring(0, firstSpace); 29.          if (firstSpace < lastSpace) 30.             middle = fullName.substring(firstSpace + 1, lastSpace); 31.          else 32.             middle = ""; 33.          last = fullName.substring(lastSpace + 1, fullName.length()); 34.       } 35.    } 36. 37.    public String toString() { 38.       StringBuffer buffer = new StringBuffer(); 39.       buffer.append(first); 40.       buffer.append(' '); 41.       if (middle.length() > 0) { 42.          buffer.append(middle.charAt(0)); 43.          buffer.append(". "); 44.       } 45.       buffer.append(last); 46.       return buffer.toString(); 47.    } 48. } 

Container-Managed Authentication and Authorization

In the preceding sections you saw how a web application can use an LDAP directory to look up user information. It is up to the application to use that information appropriately, to allow or deny users access to certain resources. In this section, we discuss an alternative approach: container-managed authentication. This mechanism puts the burden of authenticating users on the servlet container (such as Tomcat). It is much easier to ensure that security is handled consistently for an entire Web application if the container manages autentication and authorization. The application programmer can then focus on the flow of the web application without worrying about user privileges.

Most of the configuration details in this chapter are specific to Tomcat, but other servlet containers have similar mechanisms.

To protect a set of pages, you specify access control information in the web.xml file. For example, the following security constraint restricts all pages in the protected subdirectory to authenticated users that have the role registereduser or invitedguest.

 

 <security-constraint>    <web-resource-collection>       <url-pattern>/protected/*</url-pattern>    </web-resource-collection>    <auth-constraint>       <role-name>registereduser</role-name>       <role-name>invitedguest</role-name>     </auth-constraint> </security-constraint> 

The role of a user is assigned during authentication. Roles are stored in the user directory together with user names and passwords.

NOTE

graphics/note_icon.gif

If JSF is configured to use a /faces prefix for JSF pages, then you must add a corresponding URL pattern to the security constraint, such as /faces/protected/* in the preceding example.


Next, you need to specify how users authenticate themselves. The most flexible approach is form-based authentication. Add the following entry to web.xml:

 

 <login-config>    <auth-method>FORM</auth-method>    <form-login-config>       <form-login-page>/login.html</form-login-page>       <form-error-page>/noauth.html</form-error-page>    </form-login-config> </login-config> 

The form login configuration specifies a web page into which the user types in the username and password. You are free to design any desired appearance for the login page, but you must include a mechanism to submit a request to j_security_check with request parameters named j_username and j_password. The following form will do the job:

 

 <form method="POST" action="j_security_check">    User name: <input type="text" name="j_username"/>    Password:  <input type="password" name="j_password"/>    <input type="submit" value="Login"/> </form> 

The error page can be any page at all.

When the user requests a protected resource, the login page is displayed (see Figure 10-14). If the user supplies a valid username and password, then the requested page appears. Otherwise, the error page is shown.

Figure 10-14. Requesting a Protected Resource

graphics/10fig14.jpg


NOTE

graphics/note_icon.gif

To securely transmit the login information from the client to the server, you should use SSL. Configuring a server for SSL is beyond the scope of this book. For more information, turn to http://jakarta.apache.org/tomcat/tomcat-5.0-doc/ssl-howto.html.


You can also specify "basic" authentication by placing the following login configuration into web.xml:

 

 <login-conf>    <auth-method>BASIC</auth-method>    <realm-name>This string shows up in the dialog</realm-name> </login-conf> 

In that case, the browser pops up a password dialog (see Figure 10-15). However, a professionally designed web site will probably use form-based authentication.

Figure 10-15. Basic Authentication

graphics/10fig15.jpg


The web.xml file only describes which resources have access restrictions and which roles are allowed access. It is silent on how users, passwords, and roles are stored. You configure that information by specifying a realm for the web application. A realm is any mechanism for looking up user names, passwords, and roles. Tomcat supports several standard realms that access user information from one of the following sources:

  • An LDAP directory

  • A relational database

  • An XML file (by default, conf/tomcat-users.xml) that is read when the server starts

To configure a realm, you supply a Realm element. Listing10-17 shows a typical example, a JNDI realm.

Listing 10-17. accesscontrol/META-INF/context.xml
  1. <Context path="/accesscontrol" docbase="webapps/accesscontrol.war">  2. <Realm className="org.apache.catalina.realm.JNDIRealm"  3.    debug="99"  4.    connectionURL="ldap://localhost:389"  5.    connectionName="cn=Manager,dc=corejsf,dc=com"  6.    connectionPassword="secret"  7.    userPattern="uid={0},ou=people,dc=corejsf,dc=com"  8.    userPassword="userPassword"  9.    roleBase="ou=groups,dc=corejsf,dc=com" 10.    roleName="cn" 11.    roleSearch="(uniqueMember={0})"/> 12. </Context> 

The configuration lists the URL and login information and describes how to look up users and roles.

In this example, the Realm element is placed inside a Context element in the file META-INF/context.xml. This is the preferred mechanism for supplying an application-specific realm.

CAUTION

graphics/caution_icon.gif

You can also configure a realm in the Engine or Host element of the server.xml file. However, that realm is then used by the manager application in addition to your regular web application. If you want to use the manager application to install your web applications, then you must make sure that the username and password that you use for installation is included in the realm, with a role of manager.


Since the servlet container is in charge of authentication and authorization, there is nothing for you to program. Nevertheless, you may want to have programmatic access to the user information. The HttpServletRequest yields a small amount of information, in particular, the name of the user who logged in. You get the request object from the external context:

 

 ExternalContext external       = FacesContext.getCurrentInstance().getExternalContext(); HttpServletRequest request    = (HttpServletRequest) external.getRequest(); String user = request.getRemoteUser(); 

You can also test whether the current user belongs to a given role. For example,

 

 String role = "admin"; boolean isAdmin = request.isUserInRole(role); 

NOTE

graphics/note_icon.gif

Currently, there is no specification for logging off or for switching identities when using container-managed security. This is a problem, particularly for testing web applications. Tomcat uses cookies to represent the current user, and you need to quit and restart your browser whenever you want to switch your identity. We resorted to using Lynx for testing because it starts up much faster than a graphical web browser see Figure 10-16.

Figure 10-16. Using Lynx for Testing a Web Application

graphics/10fig16.jpg



We give you a skeleton application that shows container-managed security at work. When you access the protected resource protected/welcome.jsp (Listing 10-18), then the authentication dialog of Listing 10-21 is displayed. You can proceed only if you enter a username and password of a user belonging to the registereduser or invitedguest role.

Just to demonstrate the servlet API, the welcome page shows the name of the registered user and lets you test for role membership (see Figure 10-16).

Listing 10-18. accesscontrol/protected/welcome.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.          <title><h:outputText value="#{msgs.title}"/></title>  8.       </head>  9.       <body> 10.          <h:form> 11.             <p><h:outputText value="#{msgs.youHaveAccess}"/></p> 12.             <h:panelGrid columns="2"> 13.                <h:outputText value="#{msgs.yourUserName}"/> 14.                <h:outputText value="#{user.name}"/> 15. 16.                <h:panelGroup> 17.                   <h:outputText value="#{msgs.memberOf}"/> 18.                   <h:selectOneMenu onchange="submit()" value="#{user.role}"> 19.                      <f:selectItem itemValue="" itemLabel="Select a role"/> 20.                      <f:selectItem itemValue="admin" itemLabel="admin"/> 21.                      <f:selectItem itemValue="manager" itemLabel="manager"/> 22.                      <f:selectItem itemValue="registereduser" 23.                         itemLabel="registereduser"/> 24.                      <f:selectItem itemValue="invitedguest" 25.                          itemLabel="invitedguest"/> 26.                   </h:selectOneMenu> 27.                </h:panelGroup> 28.                <h:outputText value="#{user.inRole}"/> 29.             </h:panelGrid> 30.          </h:form> 31.       </body> 32.    </f:view> 33. </html> 

Listing 10-19. accesscontrol/WEB-INF/web.xml
  1. <?xml version="1.0"?>  2. <!DOCTYPE web-app PUBLIC  3.    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  4.    "http://java.sun.com/dtd/web-app_2_3.dtd">  5.  6. <web-app>  7.    <servlet>  8.       <servlet-name>Faces Servlet</servlet-name>  9.       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> 10.       <load-on-startup>1</load-on-startup> 11.    </servlet> 12. 13.    <servlet-mapping> 14.       <servlet-name>Faces Servlet</servlet-name> 15.       <url-pattern>*.faces</url-pattern> 16.    </servlet-mapping> 17. 18.    <welcome-file-list> 19.       <welcome-file>index.html</welcome-file> 20.    </welcome-file-list> 21. 22.    <security-constraint> 23.       <web-resource-collection> 24.          <web-resource-name>Protected Pages</web-resource-name> 25.          <url-pattern>/protected/*</url-pattern> 26.       </web-resource-collection> 27.       <auth-constraint> 28.          <role-name>registereduser</role-name> 29.       <role-name>invitedguest</role-name> 30.       </auth-constraint> 31.     </security-constraint> 32. 33.    <login-config> 34.       <auth-method>FORM</auth-method> 35.       <form-login-config> 36.          <form-login-page>/login.html</form-login-page> 37.          <form-error-page>/noauth.html</form-error-page> 38.       </form-login-config> 39.    </login-config> 40. 41.     <security-role> 42.       <role-name>registereduser</role-name> 43.     </security-role> 44.     <security-role> 45.       <role-name>invitedguest</role-name> 46.     </security-role> 47. </web-app> 

Listing 10-20. accesscontrol/WEB-INF/classes/com/corejsf/UserBean.java
 1. package com.corejsf; 2. 3. import java.util.logging.Logger; 4. import javax.faces.context.ExternalContext; 5. import javax.faces.context.FacesContext; 6. import javax.servlet.http.HttpServletRequest; 7. 8. public class UserBean { 9.     private String name; 10.    private String role; 11.    private Logger logger = Logger.getLogger("com.corejsf"); 12. 13.    public String getName() { 14.       if (name == null) getUserData(); 15.       return name == null ? "" : name; 16.    } 17. 18.    public String getRole() { return role == null ? "" : role; } 19.    public void setRole(String newValue) { role = newValue; } 20. 21.    public boolean isInRole() { 22.       ExternalContext context 23.          = FacesContext.getCurrentInstance().getExternalContext(); 24.       Object requestObject =  context.getRequest(); 25.       if (!(requestObject instanceof HttpServletRequest)) { 26.          logger.severe("request object has type " + requestObject.getClass()); 27.          return false; 28.       } 29.       HttpServletRequest request = (HttpServletRequest) requestObject; 30.       return request.isUserInRole(role); 31.    } 32. 33.    private void getUserData() { 34.       ExternalContext context 35.          = FacesContext.getCurrentInstance().getExternalContext(); 36.       Object requestObject =  context.getRequest(); 37.       if (!(requestObject instanceof HttpServletRequest)) { 38.          logger.severe("request object has type " + requestObject.getClass()); 39.          return; 40.       } 41.       HttpServletRequest request = (HttpServletRequest) requestObject; 42.       name = request.getRemoteUser(); 43.    } 44. } 

Listing 10-21. accesscontrol/login.html
  1. <html>  2.    <head>  3.       <title>Login Form</title>  4.    </head>  5.  6.    <body>  7.       <form method="POST" action="j_security_check">  8.          <p>You need to log in to access protected information.</p>  9.          <table> 10.             <tr> 11.                <td>User name:</td> 12.                <td> 13.                   <input type="text" name="j_username"/> 14.                </td> 15.             </tr> 16.             <tr> 17.                <td>Password:</td> 18.                <td> 19.                   <input type="password" name="j_password"/> 20.                </td> 21.             </tr> 22.          </table> 23.          <input type="submit" value="Login"/> 24.       </form> 25.    </body> 26.  </html> 

Figure 10-17. Welcome Page of the Authentication Test Application

graphics/10fig17.jpg


Figure 10-18 shows the directory structure of the application. The web.xml file in Listing 10-22 restricts access to the protected directory. Listing 10-23 contains the page that is displayed when authorization fails. Listing 10-25 contains the protected page. You can find the message strings in Listing 10-26 and the code for the user bean in Listing 10-24.

Listing 10-22. accesscontrol/WEB-INF/web.xml
  1. <?xml version="1.0"?>  2. <!DOCTYPE web-app PUBLIC  3.    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  4.    "http://java.sun.com/dtd/web-app_2_3.dtd">  5.  6. <web-app>  7.    <servlet>  8.       <servlet-name>Faces Servlet</servlet-name>  9.       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> 10.       <load-on-startup>1</load-on-startup> 11.    </servlet> 12. 13.    <servlet-mapping> 14.       <servlet-name>Faces Servlet</servlet-name> 15.       <url-pattern>*.faces</url-pattern> 16.    </servlet-mapping> 17. 18.    <welcome-file-list> 19.       <welcome-file>index.html</welcome-file> 20.    </welcome-file-list> 21. 22.    <security-constraint> 23.       <web-resource-collection> 24.          <web-resource-name>Protected Pages</web-resource-name> 25.          <url-pattern>/protected/*</url-pattern> 26.       </web-resource-collection> 27.       <auth-constraint> 28.          <role-name>registereduser</role-name> 29.       <role-name>invitedguest</role-name> 30.       </auth-constraint> 31.     </security-constraint> 32. 33.    <login-config> 34.       <auth-method>FORM</auth-method> 35.       <form-login-config> 36.          <form-login-page>/login.html</form-login-page> 37.          <form-error-page>/noauth.html</form-error-page> 38.       </form-login-config> 39.    </login-config> 40. 41.     <security-role> 42.       <role-name>registereduser</role-name> 43.     </security-role> 44.     <security-role> 45.       <role-name>invitedguest</role-name> 46.     </security-role> 47. </web-app> 

Listing 10-23. accesscontrol/noauth.html
 1. <html> 2.    <head> 3.       <title>Authentication failed</title> 4.    </head> 5. 6.    <body> 7.       <p>Sorry--authentication failed. Please try again.</p> 8.    </body> 9.  </html> 

Listing 10-24. accesscontrol/WEB-INF/classes/com/corejsf/UserBean.java
  1. package com.corejsf;  2.  3. import java.util.logging.Logger;  4. import javax.faces.context.ExternalContext;  5. import javax.faces.context.FacesContext;  6. import javax.servlet.http.HttpServletRequest;  7.  8. public class UserBean {  9.    private String name; 10.    private String role; 11.    private Logger logger = Logger.getLogger("com.corejsf"); 12. 13.    public String getName() { 14.       if (name == null) getUserData(); 15.       return name == null ? "" : name; 16.    } 17. 18.    public String getRole() { return role == null ? "" : role; } 19.    public void setRole(String newValue) { role = newValue; } 20. 21.    public boolean isInRole() { 22.       ExternalContext context 23.          = FacesContext.getCurrentInstance().getExternalContext(); 24.       Object requestObject =  context.getRequest(); 25.       if (!(requestObject instanceof HttpServletRequest)) { 26.          logger.severe("request object has type " + requestObject.getClass()); 27.          return false; 28.       } 29.       HttpServletRequest request = (HttpServletRequest) requestObject; 30.       return request.isUserInRole(role); 31.    } 32. 33.    private void getUserData() { 34.       ExternalContext context 35.          = FacesContext.getCurrentInstance().getExternalContext(); 36.       Object requestObject =  context.getRequest(); 37.       if (!(requestObject instanceof HttpServletRequest)) { 38.          logger.severe("request object has type " + requestObject.getClass()); 39.          return; 40.       } 41.       HttpServletRequest request = (HttpServletRequest) requestObject; 42.       name = request.getRemoteUser(); 43.    } 44. } 

Listing 10-25. accesscontrol/protected/welcome.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.          <title><h:outputText value="#{msgs.title}"/></title>  8.       </head>  9.       <body> 10.          <h:form> 11.             <p><h:outputText value="#{msgs.youHaveAccess}"/></p> 12.             <h:panelGrid columns="2"> 13.                <h:outputText value="#{msgs.yourUserName}"/> 14.                <h:outputText value="#{user.name}"/> 15. 16.                <h:panelGroup> 17.                   <h:outputText value="#{msgs.memberOf}"/> 18.                   <h:selectOneMenu onchange="submit()" value="#{user.role}"> 19.                      <f:selectItem itemValue="" itemLabel="Select a role"/> 20.                      <f:selectItem itemValue="admin" itemLabel="admin"/> 21.                      <f:selectItem itemValue="manager" itemLabel="manager"/> 22.                      <f:selectItem itemValue="registereduser" 23.                          itemLabel="registereduser"/> 24.                      <f:selectItem itemValue="invitedguest" 25.                          itemLabel="invitedguest"/> 26.                   </h:selectOneMenu> 27.                </h:panelGroup> 28.                <h:outputText value="#{user.inRole}"/> 29.             </h:panelGrid> 30.          </h:form> 31.       </body> 32.    </f:view> 33. </html> 

Listing 10-26. accesscontrol/WEB-INF/classes/com/corejsf/messages.properties
 1. title=Authentication successful 2. youHaveAccess=You now have access to protected information! 3. yourUserName=Your user name 4. memberOf=Member of 

 

graphics/api_icon.gif

 

 javax.servlet.HttpServletRequest 

  • String getRemoteUser() [Servlet 2.2]

    Gets the name of the user who is currently logged in, or null if there is no such user.

  • boolean isUserInRole(String role) [Servlet 2.2]

    Tests whether the current user belongs to the given role.

Figure 10-18. Directory Structure of the Access Control Application

graphics/10fig18.jpg




core JavaServer Faces
Core JavaServer Faces
ISBN: 0131463055
EAN: 2147483647
Year: 2003
Pages: 121

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net