[an error occurred while processing this directive]
> developer > web app development
Securing J2EE 1.2 Applications
by Martin Holzner, Lead Consulting Eng. , Novell
Date Created: 2002-04-26 11:01:00.000
  Securing the Web Tier
  Securing the Business Tier
  Resources

(The code base for this article is the EAR project from my last article)

With my last few articles, I showed you how to write a basic servlet, how to package it, and how to deploy it. I later showed you how to do these same things using better tool support. I also added a simple EJB to the mix. All of this is nice, but so far everything is wide open. Hence, whoever can get to our network can access everything. On the other hand, we don't want to hardcode user names. Fortunately, J2EE comes with a built in security concept, which is based on roles that are provided in the deployment descriptors. These roles have to be mapped to a physical user or user group at deployment time. So your code always contains the current user principal, and you can check whether the current user is part of a given role name. Let me show you how it works.

securing the web tier
First, let's secure our servlet. We want only users who are part of a certain role to be able to access this servlet (and any other HTTP resource in the WAR).

The good news is we don't have to code a single line to do this. It can all be done in the descriptors, so it's very flexible and portable. We can lock down HTTP resources individually or as groups of URIs, and we can treat different HTTP methods differently. For instance, we can allow GETs to all resources for everybody, but POSTs only to jsps and only for admins. So, what do we need to do to lock down the WAR we created?

Locking Down HTTP Resources

  • Open the WAR's deployment descriptor
  • Add a security constraint

    right click on "Security Constraint" and choose "add"

  • Add a Web resource collection to the security constraint

    right click on "Security Constraint" and choose "add Web Resource Collection"

    right click on "UntitledWebResource" and choose "Properties"

    give it a name (like all URIs), and add a URL Pattern of "/*"

    (Leaving the HTTP methods empty applies this constraint to all.)

  • Add an authorization constraint to the security constraint

    right click on "Security Constraint" and choose "Add Authorization Constraint"

    right click on "Authorization Constraint" and choose "Properties"

    click on "Edit Roles" and change "Authorization Constraint" to "ROLE1"

  • Add a login configuration

    right click on "Login Configuration" and choose "Add"

    choose "Basic" as the authentication mechanism and "default" as the realm name

    At this point you'll need to get around an XWB bug, so switch to XML view and change the XML as follows:

        <login-config>

           <auth-method>BASIC</auth-method>

           <realm-name>default</realm-name>

        </login-config>

    (If you don't do this, you won't be able to save the descriptor.)

  • Add a role

    right click on "Roles" and choose "Add"

    right click on "UntitledRole" and choose "Properties"

    name the role "ROLE1"

    That's all. Now we just need to map "ROLE1" to a physical user or a user group. This has to be done in the deployment plan.

  • Open the EAR's deployment plan
  • Under the Web module "WAR1.war" you'll see a "Roles" node, and under that ROLE1. Map the role now.

    right click on "ROLE1" and choose "Properties"

    in the properties dialog either manually enter a user or group,

    or choose from the menu if there is a valid server profile and the server is up and running

  • Save the file and deploy the archive

    To test this new security setting, point your browser to http://server/db/WAR1/servlet. You will be asked for a user name and password. After providing a valid user name, either the one you mapped or one from the group you mapped, you will be allowed to access the servlet. If you do not provide a valid user name and password, you will see a standard message of some sort (e.g., like "you must login before you can access this page...").

Write Your Own Login Page

If your browser's default login dialog is not enough, no problem! You can use form-based, certificate-based, or digest-based security as well, which will allow you to provide your own login dialog. Here I'll show you how to use form-based security to create your own login dialog. For this purpose the J2EE spec requires the container to provide a URL with the name "j_security_check". This URI will be our action URL for the login form. The spec defines the two text fields for user name and password as well. The user name field has to be named "j_username" and the password field "j_password". Now let's create our own login dialog using a JSP.

Here is some sample code:

<@page language="java"

session="true"

isThreadSafe="false"

contentType="text/html" %>

<html>

    <head>

       <title>Login to my J2EE app</title>

       <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

    </head>

    <body bgcolor="#FFFFFF">

    <P><A href="<%=response.encodeURL(request.getContextPath()) %>">Home</A></P>

    <FORM METHOD="POST" ACTION="<%=response.encodeURL("j_security_check")%>">

    <table width="23%" border="0" cellpadding="2">

       <tr>

          <td width="9%">User</td>

          <td width="91%">

             <input name="j_username" type="TEXT">

          </td>

       </tr>

       <tr>

          <td width="9%">Pwd</td>

          <td width="91%">

             <input name="j_password" type="PASSWORD">

          </td>

       </tr>

       <tr>

          <td width="9%"> </td>

          <td width="91%">

                <div align="left">

                   <input name="btnLogin" type="submit" value="login!">

                </div>

          </td>

       </tr>

    </table>

    </FORM>

    </body>

</html>

You can use the new JSP wizard in SilverStream eXtend Developer Workbench (XWB) to create two simple JSPs, "Login.jsp" and "LoginError.jsp", and paste this sample in the Login.jsp. Make sure you add the JSPs to the WAR project. The wizard will ask you for this information, but its easy to accidentally skip over it, which will put your JSPs in the EAR instead of the WAR.

Now we'll change the deployment descriptor for the WAR. There can only be one login configuration for a WAR, so we need to change our "BASIC" configuration to "FORM" and reference our newly created Login.jsp and LoginError.jsp as "Form Login Page" and "Form Error Page" respectively.

Our login configuration should look like this now:

<login-config>

    <auth-method>FORM</auth-method>

    <realm-name>default</realm-name>

    <form-login-config>

       <form-login-page>Login.jsp</form-login-page>

       <form-error-page>LoginError.jsp</form-error-page>

    </form-login-config>

</login-config>

For a change, let's secure a single URI in our WAR. To do this, change the Web resource collection's URL pattern to only protect servlet1:

<web-resource-collection>

    <web-resource-name>AllWebResource</web-resource-name>

    <url-pattern>/servlet1</url-pattern>

</web-resource-collection>

Deploy the EAR and point your browser to the servlet again. The Login.jsp will come up, unless we didn't provide the correct information, in which case we'll be forwarded to the LoginError.jsp.

Programmatic Security

In case the declarative options aren't going far enough for you, there's still a way to achieve fine-grained security in your code. The "javax.servlet.http.HttpServletRequest" servlet offers three methods for security checks. However, you are strongly encouraged to stay away from programmatic security as much as you can. J2EE 1.3 tries to get around this by including the JAAS spec (Java Authorization and Authentication Services). But covering JAAS would require at least one more article, so for now keep in mind: If you really need to use programmatic security, make sure you encapsulate the calls in delegates to protect your main code and keep it open for future enhancements by the specs. Here are the three methods exposed by the HttpServletRequest:

  • getRemoteUser - The user name that the client used to authenticate
  • isUserInRole - Determines if the current user is a member of the given role
  • getUserPrincipal - Returns a "java.security.Principal"
securing the business tier
EJBs can be secured similarly to Web resources. Once again the spec encourages you to use declarative security defined in the deployment descriptor for the EJB jar. Every EJB that you want to secure has to be locked down individually. There's no group function like there is for HTTP resources. Every method of the EJB, and therefore all the methods of the remote and home interface, can be secured for one or more roles. Each used role has to be defined as a security role in the EJB jars descriptor. At deployment time, each role must be mapped to a physical user or group in your server, just like it is done for WARs. In our example, if you want to allow ROLE1 to access the "callMe method of SBEjb1", you have to add the role to the EJB jars descriptor, and then add a method permission for the SBEjb1.callMe method.

  • Open the EJB jars deployment descriptor

    right click on EJB1.jar in the navigation tree and choose "Open Deployment Descriptor"

  • Open the application assembly node, and add a role

    right click on "Roles" and choose "Add"

    right click on "UntitledRole" and choose "Properties"

    name the role "ROLE2"

  • Add the method permission for the SBEjb1's callMe method

    right click on "Method Permission" and choose "Add"

    right click on "UntitledPermission" and choose "Properties"

    name it SBEjb1_callMe, add the "ROLE2" to roles, and "SBEJB1:callMe" to methods

The resulting ejb-jar.xml would look something like this:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">

<ejb-jar>

    <enterprise-beans>

       <session>

          <ejb-name>SBEjb1</ejb-name>

          <home>ejb1.SBEjb1Home</home>

          <remote>ejb1.SBEjb1</remote>

          <ejb-class>ejb1.SBEjb1Bean</ejb-class>

          <session-type>Stateless</session-type>

          <transaction-type>Bean</transaction-type>

       </session>

    </enterprise-beans>

    <assembly-descriptor>

       <security-role>

          <role-name>ROLE2</role-name>

       </security-role>

       <method-permission>

          <description>SBEjb1_callMe</description>

          <role-name>ROLE2</role-name>

          <method>

             <ejb-name>SBEjb1</ejb-name>

             <method-name>callMe</method-name>

          </method>

       </method-permission>

    </assembly-descriptor>

    <ejb-client-jar>EJB1-client.jar</ejb-client-jar>

</ejb-jar>

To allow another role to access the callMe method, all you need to do is to add the role as a security role, and then add that role as a role name to the method permission.

....

....

<assembly-descriptor>
    <security-role>
       <role-name>ADMINS</role-name>
    </security-role>
    <security-role>
       <role-name>ROLE2</role-name>
    </security-role>
    <method-permission>
       <description>SBEjb1_callMe</description>
       <role-name>ADMINS</role-name>
       <role-name>ROLE2</role-name>
       <method>
          <ejb-name>SBEjb1</ejb-name>
          <method-name>callMe</method-name>
       </method>
    </method-permission>
</assembly-descriptor>

....

....

Programmatic Security

In the event that you need programmatic security, the "javax.ejb.EJBContext" exposes two methods for this. Actually, this spec names two more methods, but they are deprecated. The two that aren't deprecated are as follows:

  • getCallerPrincipal
  • isCallerInRole

Yes, they're exactly the same as for the HttpServletRequest. But there's one significant difference. Here, you can link the coded role name to a descriptor role name in the deployment descriptor. This way, you can assemble several beans that use different role names in their code to one logical role. That role can later, at deployment time, be mapped to a user or group. This is particularly helpful when you assemble beans from different providers, and every provider uses a different role name in their code. However, in case you decide to use the same role name that is used in the bean's code, you will still need to enter the role name and role link.

Here's an sample code descriptor that uses "ROLE2ref", which is linked to ROLE2:

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">

<ejb-jar>

<enterprise-beans>

    <session>

       <ejb-name>SBEjb1</ejb-name>

       <home>ejb1.SBEjb1Home</home>

       <remote>ejb1.SBEjb1</remote>

       <ejb-class>ejb1.SBEjb1Bean</ejb-class>

       <session-type>Stateless</session-type>

       <transaction-type>Bean</transaction-type>

          <security-role-ref>

             <role-name>ROLE2ref</role-name>

             <role-link>ROLE2</role-link>

          </security-role-ref>

    </session>

</enterprise-beans>

    <assembly-descriptor>

       <security-role>

          <role-name>ADMINS</role-name>

       </security-role>

       <security-role>

          <role-name>ROLE2</role-name>

       </security-role>

       <method-permission>

          <description>allSBEjb1</description>

          <role-name>ADMINS</role-name>

          <method>

             <ejb-name>SBEjb1</ejb-name>

             <method-name>*</method-name>

          </method>

       </method-permission>

    </assembly-descriptor>

    <ejb-client-jar>EJB1-client.jar</ejb-client-jar>

</ejb-jar>

To achieve this, follow these steps:

  • Open the deployment descriptor and a role reference to SBEjb1

    right click on "Environment" under the "SBEjb1" node and choose "Add Role Reference"

    right click on "UntitledRoleReference" and choose "Properties"

    name the reference "ROLE2ref" and link it to "ROLE2"

    (Since we already added the ROLE2 role, we're done.)

To see how the role reference works, add this sample code to the SBEjb1.callMe method:

public String callMe( String msg )
{
    System.out.println("is user in role ? " + m_sessionContext.isCallerInRole("ROLE2ref"));

    return "<B>" + msg + "</B>";
}

Now all we have to do is deploy the EAR.

Experiment with these new security settings yourself. Create some users and groups (or use existing ones) and change the mapping in your deployment plan.

resources
arrow  Download the source code for this article
arrow  J2EE Home page
arrow  Sun J2EE BluePrints Index