Using Declarative Security

 < Free Open Study > 



We've covered enough theory and background regarding the operation model of Tomcat 4's declarative container-managed security - it's time to put our knowledge to work with some examples.

Using a MemoryRealm

Our first example will make use of a MemoryRealm. We'll do the following:

  • Protect the NetAccessServlet that we worked with earlier in a new web application called wroxrealms

  • Set up a MemoryRealm to supply a username, password, and role to the Tomcat container

  • Set up the declarative security by adding a <security-constraint> and <login-config> entry to the deployment descriptor

  • Test declarative security by accessing the protected NetAccessServlet

We'll define a MemoryRealm, based on the worker and supervisor scenario that we introduced earlier. The MemoryRealm implementation will take user authentication data and role information from an XML file (wroxUsers.xml) when Tomcat starts up. Tomcat requires the document element of this file be <tomcat-users>:

    <tomcat-users>      <user name="jane" password="abc123" roles="worker" />      <user name="jill" password="cde456" roles="worker" />      <user name="kim" password="efg789" roles="worker,supervisor" />    </tomcat-users> 

Next, we must configure a MemoryRealm to associate with our wroxrealms web application. We need to create a <Context> for our wroxrealms application and associating a MemoryRealm with it. Here's what needs to be added to server.xml:

    <Context path="/wroxrealms" docBase="wroxrealms" debug="0"             reloadable = "true">      <Realm className="org.apache.catalina.realm.MemoryRealm"             pathname="webapps/wroxrealms/WEB-INF/wroxUsers.xml" />    </Context> 

We use the pathname attribute of the realm element to point directly to the XML file that contains the user, password and role information. The attributes supported by the realm element depend on the type of realm used.

We need to configure container-managed security in the deployment descriptor:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <!DOCTYPE web-app        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"        "http://java.sun.com/dtd/web-app_2_3.dtd">    <web-app>      <servlet>        <servlet-name>passthru</servlet-name>        <servlet-class>NetAccessServlet</servlet-class>      </servlet> 

Access is restricted to the role of supervisor only:

      <security-constraint>        <web-resource-collection>          <web-resource-name>Access Through Servlet Name</web-resource-name>          <url-pattern>/servlet/passthru</url-pattern>        </web-resource-collection>        <auth-constraint>          <role-name>supervisor</role-name>        </auth-constraint>      </security-constraint> 

Using the <login-config> entry, we have associated the BASIC method of authentication with this web application:

      <login-config>        <auth-method>BASIC</auth-method>        <realm-name>Wrox Supervisors Only</realm-name>      </login-config>    </web-app> 

Start Tomcat 4 and navigate to http://localhost:8080/wroxrealms/servlet/passthru. We will then be prompted with the familiar login dialog:

click to expand

The realm that we have specified in the <login-config> shows up as a simple string label in this dialog. This has nothing to do with the realm that we use in Tomcat, it's just an unfortunate coincidence in terminology.

Enter a user name of "jane" and a password of "abc123". When we do this, Tomcat will perform authentication by calling the authenticate() method of the associated realm (org.apache.catalina.realm.MemoryRealm in our case). It will also call the userInRole() method of the realm to determine if the user belongs to a role that is needed to access this resource. The authentication succeeds, but Jane is not allowed to access the protected resource because she does not map to the role of supervisor.

Now, let's try to access the servlet via an unprotected backdoor. Navigate to http://localhost:8080/wroxrealms/servlet/NetAccessServlet. Jane can now access the protected servlet. Our protection specified in deployment descriptor has a loophole since a servlet can be accessed directly with its class name. In the <url-pattern> specified in the <security-constraint> element, we should have been more thorough (by using "/* ", or specifying"/servlet/*").

Many browsers will cache the username and password once authentication succeeds, so you should restart the browser before attempting authentication with another user name. Navigate to http://localhost:8080/wroxrealms/servlet/passthru and use the user name "kim" and password "efg789". This time, you will notice that authentication succeeds and you will be able to access NetAccessServlet.

Using Digested Passwords

Let's modify the example to use digested passwords, which is supported by the MemoryRealm that we use. Shutdown Tomcat, and make the following amendments to server.xml:

    <Context path="/wroxrealms" docBase="wroxrealms" debug="0"             reloadable="true">      <Realm className="org.apache.catalina.realm.MemoryRealm"             pathname="webapps/wroxrealms/WEB-INF/wroxUsersDigested.xml"             digest="MD5" />    </Context> 

wroxUsersDigested.xml has replaced the clear text password with their MD5 hashed equivalents:

    <tomcat-users>      <user name="jane" password="e99a18c428cb38d5f260853678922e03"            roles="worker" />      <user name="jill" password="b7e64172f775d1df1348da7e925be164"            roles="worker" />      <user name="kim" password="236ef0ed108857aecd3d328485e402da"            roles="worker,supervisor" />    </tomcat-users> 

The MemoryRealm implementation in Tomcat acts also as a standalone password hashing utility. We can use the following command to obtain the hash string for "abc123":

    java -classpath %CATALINA_HOME%\server\lib\catalina.jar    org.apache.catalina.realm.RealmBase -a MD5 abc123 

The other two passwords for user kim and jill are hashed in the same way. Start Tomcat and navigate to http://localhost:8080/wroxrealms/servlet/passthru. Using the identity of "kim" and then "jane" the behavior stays exactly the same. BASIC authentication is still used as specified by the <login-config> associated with the web application. However, the wroxUsers.xml file now contains the digested passwords that are difficult to decode.

Using Form Based Authentication

It is possible to supply our own customized web pages to authenticate the user, instead of using the default dialog box supplied by the browser. First, we must modify the <login-config> element in the deployment descriptor. Instead of using the BASIC authentication method, we now use the FORM method. We specify <form-login-page> and <form-error-page> elements that will define our login and error pages. These paths are specified relative to the root of the web application:

      <login-config>        <auth-method>FORM</auth-method>        <realm-name>Wrox Supervisors Only</realm-name>        <form-login-config>           <form-login-page>/formlogin/login.htm</form-login-page>           <form-error-page>/formlogin/error.htm</form-error-page>        </form-login-config>      </login-config> 

We need to create additional web pages to authenticate the user. First, we create login.htm:

    <html>      <head>        <title>Customized Page for Authentication</title>        <style>          body, table, td {background-color:#FFFFFF; color:#000000;                           font-family:verdana; font-size:10pt;}          input {font-family:verdana; font-size:10pt;}        </style>      </head>      <body>        <h3>Wrox Login</h3>        <form method="post" action="j_security_check">          <table>            <tr>             <td>User Name:</td>             <td><input type="text" name="j_username"></td>            </tr>            <tr>              <td>Password:</td>              <td><input type="password" name="j_password"></td>            </tr>          </table>          <p><input type="submit" value="Authenticate"></p>        </form>      </body>    </html> 

start sidebar

The action associated with the POST for the form must be j_security_check in order for the authentication to be handled properly. The user name must be contained in a field called j_username, and the password in j_password.

end sidebar

The error page (error.htm) will be used to indicate a login error:

    <html>      <head>        <title>Error Page for Authentication</title>        <style>          body {background-color:#FF3333; color:#FFFFFF;                font-family:verdana; font-size:10pt;}        </style>      </head>      <body>        <h3>Wrox Security</h3>        <p><b>Sorry, your login request has been denied.</b></p>      </body>    </html> 

Start Tomcat 4 and navigate to http://localhost:8080/wroxrealms/servlet/passthru in order to access the protected servlet. Instead of the rather mundane authentication dialog box, you should now see our custom form:

click to expand

If you enter the user name "jane", and password "bbbccc" our error page will be displayed:

click to expand

Let's attempt login again. Enter the user name "kim" and password "efg789" in login.htm. As expected, we are allowed to access the protected URL.

Finally, let's try again using "jane" and the correct password "abc123" - remember to restart your browser first, to flush the cashed credentials. When you try it this time, you will (perhaps surprisingly) see this page:

click to expand

This is the correct behavior because:

  • The authentication is successful, and user "jane" is authenticated - therefore there is no login error, so the error page is not displayed

  • The user "jane", does not map to the supervisor role required to access the resource and so is denied access

Using a JDBCRealm

A JDBCRealm accesses user login information and role mapping information through a JDBC source, instead of the simple XML file as in MemoryRealm. The utility of a JDBCRealm is not restricted to only relational database management systems, it can be used to access information from any storage facility that exposes a JDBC-compatible interface.

In order for a JDBC source to work with a JDBCRealm, the database storing the mapping information must have specific table layout:

click to expand

The actual name of the table and fields in the JDBC source are not specified by the Realm implementation, but can be assigned at deployment time based on the attributes of the <Realm> element.

In the user table, the following fields are mandatory:

Required Field

Description

username

The name of the user for authentication

password

Contains the password associated with the user

The user role table consists of the following mandatory fields:

Required Field

Description

username

The name of the user for authentication

role

One of the roles that the user is mapped to

In the user role table, there must be one row for each role that a user is associated with. The tables used can in fact contain other fields, however, the JDBCRealm implementation will only make use of the required fields noted above.

As with the MemoryRealm, or any other realm implementation, the definition for the realm is associated with either a <Context>, <Host>, or <Engine> and appears in the server.xml file. For JDBCRealm, the following attributes of the <Realm> element are important:

Attribute Name

Description

classname

Must be for org.apache.catalina.realm.JDBCRealm Tomcat 4's JDBCRealm implementation

driverName

The Java class name of the JDBC driver, used in typical JDBC programming

connectionName

The name of the JDBC connection to access

connectionPassword

The password used when making the JDBC connection

connectionURL

The database URL used to locating the JDBC source

userTable

The name of the table that contains the two required fields of a user table

userRoleTable

The name of the table that contains the two required fields of a user role table

userNameCol

The name of the field, in both the userTable and the userRoleTable, which contains the username information (both fields must have the same name across the two tables)

userCredCol

The name of the field in the userTable that contains the password of the user

roleNameCol

The name of the field in the userRoleTable that contains a role associated with the user

We'll use MySQL and the MM.MySQL JDBC driver in our example, although the example could be easily adapted for any other database and JDBC driver. Use the following SQL script to create the database and tables, and supply sample data:

    CREATE DATABASE realmDB;    USE realmDB;    CREATE TABLE deptusers (      wroxusername VARCHAR(15) NOT NULL PRIMARY KEY,      password     VARCHAR(15) NOT NULL    );    CREATE TABLE deptroles (      wroxusername VARCHAR(15) NOT NULL,      wroxrole     VARCHAR(15) NOT NULL,      PRIMARY KEY (wroxusername, wroxrole)    );    INSERT INTO deptusers VALUES ('jane', 'abc123');    INSERT INTO deptusers VALUES ('jill', 'cde456');    INSERT INTO deptusers VALUES ('kim', 'efg789');    INSERT INTO deptroles VALUES ('jane', 'worker');    INSERT INTO deptroles VALUES ('jill', 'worker');    INSERT INTO deptroles VALUES ('kim', 'worker');    INSERT INTO deptusers VALUES ('kim', 'supervisor'); 

Now, we have everything to fill out the attributes of the JDBCRealm:

Attribute

Our Value

className

org.apache.catalina.realm.JDBCRealm

driverName

org.gjt.mm.mysql.Driver

connectionURL

jdbc:mysql://localhost/realmDB

userTable

deptusers

userRoleTable

deptroles

userNameCol

wroxusername

userCredCol

password

roleNameCol

wroxrole

We should replace our previously associated MemoryRealm for the wroxrealms web application with this new JDBCRealm. Here is the <Realm> element that we must add:

    <Context path="/wroxrealms" docBase="wroxrealms" debug="0"             reloadable="true">      <Realm className="org.apache.catalina.realm.JDBCRealm"             driverName="org.gjt.mm.mysql.Driver"             connectionURL="jdbc:mysql://localhost/realmDB"             userTable="deptusers" userRoleTable="deptroles"             userNameCol="wroxusername" userCredCol="password"             roleNameCol="wroxrole" />    </Context> 

Make sure you have the MySQL running, start Tomcat, and navigate to http://localhost:8080/wroxrealms/servlet/passthru. The custom form based login will be displayed, so enter in "jill" for the user and "cde456" for the password. Access to the servlet will be denied.

Restart your browser to clear the credentials cached, and try entering "kim" and "efg789" for the user and password respectively. Kim is allowed to access the protected servlet because she has a role of supervisor, this time authenticated through the newly configured JDBCRealm.

Imagine the scenario in which the user "jill" has been promoted to a supervisor. We simply need to use MySQL to add a single row to the deptroles table within the database:

    INSERT INTO deptroles VALUES ('jill', 'supervisor'); 

Restart your browser, and try authenticating with "jill" and "cde456" as the user and password once more. Immediately upon promotion, the user "jill" can now access the protected resource - without having even to restart Tomcat.

start sidebar

The ability to reflect dynamic change in the authentication data and role mapping is a pre-requisite for any production environment - and Tomcat's JDBCRealm implementation satisfies this requirement.

end sidebar

Multiple Authentication Requests

Now we'll illustrate a potential problem that users may encounter. We'll deploy two trivial web applications, wroxapp1 and wroxapp2 on the same virtual host (each will have its own context of course). Each application contains a servlet provides a link to the other web application. The only difference between the servlets is their name. One is called TrivialServlet1 and the other TrivialServlet2. We show TrivialServlet1 here:

    import javax.servlet.*;    import javax.servlet.http.*;    import java.io.*;    public class TrivialServlet1 extends HttpServlet {      public void doGet(HttpServletRequest request,                        HttpServletResponse response)          throws IOException, ServletException{        PrintWriter out = response.getWriter();        out.println("<html><head><style>");        out.println("body {background-color:#FFFFFF;color:#000000;" +                    "font-family:verdana;font-size:10pt;}");        out.println("</style></head>");        out.println("<body>");        out.println("Thank you for visiting Service <br><h1>#1</h1><p>");        out.println("Please visit our " +                    "<ahref=\"http://localhost:8080/wroxapp2/servlet/trivial\">"                    + other service</a>.");        out.println("</body></html>");      }    } 

We have to associate the JDBCRealm with the <Host> entry instead of individual <Context> entries. Here is the configuration for wroxapp1 and wroxapp2 that needs to be added to server.xml:

    <Realm className="org.apache.catalina.realm.JDBCRealm"           driverName="org.gjt.mm.mysql.Driver"           connectionURL="jdbc:mysql://localhost/realmDB"           userTable="deptusers" userRoleTable="deptroles"           userNameCol="wroxusername" userCredCol="password"           roleNameCol="wroxrole" />    <Context path="/wroxapp1" docBase="wroxapp1" debug="0" reloadable = "true">    </Context>    <Context path="/wroxapp2" docBase="wroxapp2" debug="0" reloadable = "true">    </Context> 

So, wroxapp1 and wroxapp2 share quite a few things in common: the same virtual host, the same authentication realm, and a very similar servlet class. However, there is one key difference, the deployer of wroxapp1 has opted to configure the BASIC method of authentication, whereas the deployer of wroxapp2 has chosen to configure the FORM method of authentication. The deployment descriptor for wroxapp1 is:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <!DOCTYPE web-app        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"        "http://java.sun.com/dtd/web-app_2_3.dtd">    <web-app>      <servlet>        <servlet-name>trivial</servlet-name>        <servlet-class>TrivialServlet1</servlet-class>      </servlet>      <security-constraint>        <web-resource-collection>          <web-resource-name>Protected Servlet</web-resource-name>          <url-pattern>/servlet/trivial</url-pattern>        </web-resource-collection>        <auth-constraint>           <role-name>supervisor</role-name>           <role-name>worker</role-name>        </auth-constraint>      </security-constraint>      <login-config>        <auth-method>BASIC</auth-method>        <realm-name>All Wrox Users</realm-name>      </login-config>    </web-app> 

While the web.xml deployment descriptor for wroxapp2 is:

    <?xml version="1.0" encoding="ISO-8859-1"?>    <!DOCTYPE web-app        PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"        "http://java.sun.com/dtd/web-app_2_3.dtd">    <web-app>       <servlet>        <servlet-name>trivial</servlet-name>        <servlet-class>TrivialServlet2</servlet-class>      </servlet>      <security-constraint>        <web-resource-collection>          <web-resource-name>Protected Servlet</web-resource-name>          <url-pattern>/servlet/trivial</url-pattern>        </web-resource-collection>        <auth-constraint>           <role-name>Supervisor</role-name>           <role-name>Worker</role-name>        </auth-constraint>      </security-constraint>      <login-config>        <auth-method>FORM</auth-method>           <realm-name>All Wrox Users</realm-name>           <form-login-config>            <form-login-page>/formlogin/login.htm</form-login-page>            <form-error-page>/formlogin/error.htm</form-error-page>          </form-login-config>      </login-config>    </web-app> 

Deploy the web applications and navigate to http://localhost:8080/wroxapp1/servlet/trivial. You should be prompted to enter a user name and password with a browser dialog. Enter any one of "jane", "jill", or "kim". You'll see the output of the protected TrivialServlet1:

click to expand

This servlet invites us to visit another service hosted on the same virtual host. We can try it by following the link:

click to expand

We are asked to authenticate ourselves again, this time via the custom FORM method of authentication, until we finally reach the requested service:

click to expand

Imagine a virtual host with associated services such as travel, stock quotes, news, and so on. This requirement to authenticate each time a service is accessed even though the authentication is done against the same realm would be tedious. Fortunately, we can use an elegant solution to avoid this problem - single sign-on.

Single Sign-on

Single sign-on allows a single user authentication to work across all the web applications within a single virtual host, so long as they are all authenticating against the same realm. This is a very useful feature that avoids unnecessary calls to authentication.

start sidebar

The mechanism used to achieve single sign-on is Tomcat 4 specific, and not part of the Servlet 2.3 specification requirements.

end sidebar

Single sign-on is accomplished using a valve. A valve is the system level equivalent of a filter. A valve is inserted into the request processing pipeline, and given an opportunity to modify a request if necessary. The valve caches authentication information and role mappings across web applications within the same virtual host, and automatically uses the cached information for web applications that require authentication.

This authentication and role information caching is done during the very first authentication against a web application. Therefore, if we accesses wroxapp1 first, we will encounter a BASIC authentication dialog box. If, on the other hand, we tried to access wroxapp2 first, then we would encounter the custom FORM-based authentication. Regardless of the method of authentication, we will not have to re-authenticate when we access the other service.

Let's install the Tomcat single sign-on valve and see it in action. To do so, simply add the following <Valve> element to server.xml:

    <Host name="localhost" debug="0" appBase="webapps" unpackWARs="true">    <Valve classname="org.apache.Catalina.authenticator.SingleSignOn"           debug="0"/> 

Restart Tomcat and navigate to http://localhost:8080/wroxapp1/servlet/trivial. Authenticate with one of the Wrox user credentials through the BASIC authentication box. You should now see the output of TrivialServlet1. Click on the link to TrivialServlet2. You'll no longer be required to authenticate against this totally separate web application.

start sidebar

The single sign-on valve uses HTTP cookies, so the client must have cookies enabled for it to work properly.

end sidebar



 < Free Open Study > 



Professional Java Servlets 2.3
Professional Java Servlets 2.3
ISBN: 186100561X
EAN: 2147483647
Year: 2006
Pages: 130

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