Java EJB Introduction to Securing Web Applications with JBoss and LDAP

Introduction to Securing Web Applications with JBoss and LDAP

Introduction

Goals

After reading this tutorial you should be able to:

  • Configure JBoss to authenticate users against an LDAP server (or other types
    of user information repository)
  • Design pluggable user information repositories using the Abstract Factory and Data Access Object patterns
  • Control the presentation of a web application with role-based presentation
  • Protect your EJB methods by confirming calls are authorized
  • This article gives an introduction to configuring security on JBoss and implementing
    an LDAP-based user information repository. The sample application described here
    demonstrates using LDAP with the JBoss Security Extension (JBossSX), and describes
    a simple API for managing users, roles, and groups in LDAP. In addition, this article
    demonstrates the use of the Abstract Factory pattern to implement pluggable persistence.

    An Abstract Factory is used to decouple the LDAP access classes in order that
    LDAP can easily be replaced with another type of storage. This is useful if you
    are building multiple web applications for different clients, some requiring LDAP
    storage, and others requiring a database.

    Requirements

    You’ll need the following software set up on your machine to run the sample:

  • LDAP server (eg OpenLDAP)
  • JBoss
  • Ant
  • The sample code has been tested with OpenLdap 2.0.19 built for
    Windows [2], jboss-3.0.4_tomcat-4.1.12 [8], and Ant 1.5.1 [9] on Windows 2000.
    For information on exactly how to build and deploy the sample code, please see
    the included readme file.

    Role Based Access Control

    According to the principles
    of Role Based Access Control (RBAC), users are granted membership into roles based
    on their competencies and responsibilities in the organization. The operations
    that a user is permitted to perform are based on the user’s role. Membership of
    roles can be easily withdrawn and new memberships established as the organization
    evolves. Role associations can be established when new operations are added, simplifying
    the administration and management of privileges; roles can be updated without
    updating the privileges for every user on an individual basis. The principle of
    least privilege is fundamental to RBAC; this requires identifying the user’s job
    functions, determining the minimum set of privileges required to perform that
    function, and restricting the user to a domain with those privileges and nothing
    more. In less precisely controlled systems, this is often difficult to achieve.

    Sample Schema Design

    The sample LDAP schema used here is built around the
    following entities: Users, Groups and Roles.

    In this approach, Users are assigned to Groups, and Groups are given Roles. The following example shows how this kind of design can be useful: Imagine two users, Jane and Bob. Jane is in the group user_admins, which has the user_management role. This means Jane is able to add and remove users from the system, update passwords, etc. Bob is in the group finance_admins, which has the finance_management role. This means Bob is able to view salary details, update salaries, etc. In this situation the information is kept secure, and is available only to the minimum number of people who need to have access to it (the principle of least privilege outlined above). Jane will not have any access to salary details, and Bob will not be able to accidentally remove users from the system. If it is necessary for one individual to be able to do both jobs, this is also possible; imagine a user called Superman, who is in both the user_admins group and the finance_admins group. This user will have both the finance_management and the user_management roles, and will have access to both types of information. The advantage of including the concept of groups in the schema is that it simplifies updating the access rights of collections of users, without requiring individual updates to each user.

    An entry for a user in LDIF (LDAP Data Interchange Format) format for the sample schema looks like
    this:

    
    	dn: uid=jbloggs,ou=People,dc=sample,dc=com 
    	objectClass: top 
    	objectClass: person 
    	objectClass: organizationalPerson 
    	objectClass: inetOrgPerson 
    	sn: Bloggs 
    	cn: Joe 
    	uid: jbloggs 
    	userpassword: no3XJAZeeb9AKbGNY65/masWpZE= 
    	mail: [email protected]

    The DN (Distinguished Name) entry is equivalent to a unique identifier for
    this entry; it states exactly where this user is positioned in the directory
    structure. Graphically, the sample schema looks like this:

    Security

    JBoss Authentication

    The authentication process can be triggered by a call to the standard servlet
    login mechanism specified in the servlet specification, ie a form submitting
    to the action j_security_check. The first step is therefore to create a login
    page, containing a form like this:

    	<form action="j_security_check" method="post">
    		Username: <input type="text" name="j_username" size="22"/>
    		Password: <input type="password" name="j_password" size="22"/>
    		<input type="submit" value="Login" />
    	</form>
    

    The username and password will be intercepted by the JBoss SecurityInterceptor
    and passed to the JAASSecurityManager class as Principal and Credential objects.
    It is worth noting here that if a user bookmarks a login page, or uses the browser
    back button to reach the page, they will see an error. This is a feature of
    the Tomcat implementation of the j_security_check mechanism. The next step is
    to set up the web.xml file as follows:

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

    Next, the login-config.xml file must be set up to specify that this security
    domain requires a certain set of LoginModules:

    	<application-policy name="sample_web_client_security"> 
    	  <authentication> <login-module code="org.jboss.security.ClientLoginModule" 
    	  flag="required"/> 
    	  <login-module code="com.sample.security.GenericJbossLoginModule" 
    	  flag="required"/> 
    	  </authentication> 
    	</application-policy> 
    

    Login modules within an application policy are chained . the .flag. value specifies
    whether they are required to succeed or not. The ClientLoginModule should be specified
    if you wish to use EJB security – it passes the username and credentials obtained
    during login to the org.jboss.security.SecurityAssociation class so that each
    EJB method invocation is associated with the given username and credentials. See
    below for details on how custom login modules work On login, the JAASSecurityManager
    will create a LoginContext for the security domain specified and attempt to authenticate
    the specified Principal by calling the login modules configured for that domain.
    Internally, this looks something like the following:

    
    	String name = getSecurityDomain(); 
    	CallbackHandler handler = new org.jboss.security.plugins.SecurityAssociationHandler(); 
    	handler.setSecurityInfo(principal, credential); 
    	LoginContext lc = new LoginContext(name, handler); 
    	lc.login(); 
    

    The security domain should be specified in the jboss-web.xml and jboss.xml files,
    with a declaration like this:

    	<security-domain>java:/jaas/security-example</security-domain> 
    

    The JAASSecurityManager will then get a a javax.security.Subject from the LoginContext,
    which will contain a Principal called username and a javax.security.Group called
    Roles (containing an array of this user’s roles) will be created. This is what
    allows methods like isUserInRole and getCallerPrincipal in the servlet and EJB
    containers to verify the current user.

    Custom Login Modules

    JBoss comes with
    a number of login modules out of the box, including an LDAPLoginModule that can
    be configured in the login-config.xml file. Developing custom login modules is
    however very simple, and the sample contains a custom login module for illustrative
    purposes. Custom login modules should extend the JBoss AbstractServerLoginModule
    class, overriding the validatePassword and getRoleSets methods. The GenericJbossLoginModule
    in the sample code delegates to a factory class in the deployed EAR file so it
    can obtain the list of roles and groups from the current repository, without having
    to be aware of the type of repository. The password validation itself is contracted
    out to the UserDAO implementation. In this case, the BaseLdapDAO class validates
    a user by attempting to connect to LDAP as that user:

    
    	props.setProperty(Context.SECURITY_PRINCIPAL,this.getDN(user)); 
    	props.setProperty(Context.SECURITY_CREDENTIALS, EncryptUtils.encryptSHA(user.getPassword())); 
    	... 
    	DirContext dir = new InitialDirContext(props); 
    

    A more advanced implementation
    could use a secure authentication mechanism; LDAP v3 supports SASL (Simple Authentication
    and Security Layer) to allow specifying the exact authorization mechanism, and
    the type of encryption to use. For additional security, it is also possible to
    connect to the LDAP server over SSL rather than with plain sockets. Here, we’re
    just encrypting passwords with the SHA-1 one-way hash algorithm before storage
    (this code is in the EncryptUtils class).

    Authorization

    In this sample, authorization
    is implemented on three levels; web-resource protection, role-based presentation,
    and EJB method-level security.

    Web Resource Protection

    You can specify protected resources using a security constraint in the web.xml
    file:

    	<security-constraint> 
    	  <web-resource-collection> 
    		  <web-resource-name>Sample Application</web-resource-name> 
    		  <description>Require users to authenticate</description> 
    		  <url-pattern>*.jsp</url-pattern> 
    		  <http-method>POST</http-method> 
    		  <http-method>GET</http-method> 
    	  </web-resource-collection> 
    	  <auth-constraint> 
    		<description>Only allow Authenticated_users role</description> 
    		<role-name>Authenticated_users</role-name> 
    	  </auth-constraint> 
    	  <user-data-constraint> 
    	  <description>Encryption is not required for the application in general. </description> 
    	  <transport-guarantee>NONE</transport-guarantee> 
    	  </user-data-constraint> 
    	</security-constraint> 
    

    In addition to specifying protected URL patterns in the web.xml file, it is
    also possible to place all JSP files under the web-inf directory and make them
    accessible only through servlets.

    Role-Based Presentation

    You can control presentation in a JSP based on a user’s role by using the HttpServletRequest.isUserInRole
    method:

      	<%if(request.isUserInRole("Member_admin")){%>
    ... <%}%>
    <%else{%>
    ... <%}%>

    Some tag libraries (eg Struts 1.1) provide tags encapsulating this functionality.

    Method-Level Security

    The JBoss authentication process will propagate the user’s roles to the EJB
    container, allowing the standard J2EE declarative security to be specified in
    the ejb-jar.xml file. You first need to establish a <security-role-ref>
    specifying the local role-name, then link this to the role specified in LDAP
    (the names have been kept the same here for simplicity):

    	<security-role-ref> 
    		<role-name>Member_admin</role-name> 
    		<role-link>Member_admin</role-link> 
    	</security-role-ref> 
    

    Then, in the assembly descriptor section, use the local name to protect individual methods like this:

    	<security-role> 
    		<role-name>Member_admin</role-name> 
    	</security-role> 
    	<method-permission> 
    		<role-name>Member_admin</role-name> 
    		<method> 
    			<ejb-name>MemberService</ejb-name> 
    			<method-name>secureMethod</method-name> 
    		</method> 
    	</method-permission>
    

    To test this in action, log in to the sample application as fsmith/fsmith and
    try submitting directly to the SampleServlet URL (eg http://localhost:8080/security-sample/SampleServlet?method=secure).
    You should see a stack trace as fsmith is not in the Member_admin role.

    Configuration Summary

    To summarise, the steps you need to follow to secure a
    web application are:

    1. Create a login page
    2. Set up the web.xml file
    3. Set up the login-config.xml file
    4. Modify the jboss-web.xml and jboss.xml files to include the security domain
    5. Place any custom login modules in the lib directory
    6. Add security to your EJB methods

    Persistence Layer Architecture

    Following the preceding steps should give you the foundations for a secure web application
    on JBoss. The remainder of this article gives a briefly describes a possible
    architecture for a user information repository. In a production system, you
    might choose to replace much of the JNDI-specific code here with a dedicated
    LDAP API, such as JLDAP, or the Netscape Directory API. The example code here
    uses JNDI for simplicity, but there are some advantages to using a dedicated
    API. The structure of the persistence layer is as follows:

    UserDAO, GroupDAO and RoleDAO Interfaces

    These specify the contract that must be implemented by
    Data Access Objects for a given type of repository; in this example, the implementation
    classes are LdapUserDAO, LdapGroupDAO and LdapRoleDAO.

    Abstract Factory

    The DAOFactory class is subclassed to return a concrete factory (LdapDAOFactory
    in this case), which will be used to retrieve concrete instances of the UserDAO,
    GroupDAO and RoleDAO instances. The sample EJB is hard-wired to use the LDAP
    DAO factory class. You can change this to return a different Factory class
    if necessary.

    Session Facade

    In this architecture, the UserManagement
    EJB classes act as a fagade layer in front of the persistence classes . this
    can be used to coordinate calls to more than one repository. For example, while
    some user details (eg password) are stored in LDAP, others might be stored in
    a database (eg customer ID); the fagade layer can take a UserTO object and redirect
    as appropriate. The Session Fagade is a useful pattern from the perspective
    of security, as it centralizes security management; it is relatively easy to
    implement security on the course-grained methods at this level, rather than
    deeper inside the system where there may be more methods to protect.

    LdapConfiguration

    This is a singleton class which reads in details of the current
    LDAP schema from a properties file. The LDAP schema used here is simple, so
    this class is trivial, but in a more complex system, it might be necessary to
    read in all of the schema details in order to remain flexible.

    Data Transfer Objects

    The UserTO, GroupTO, and RoleTO classes are simple value objects used
    to reduce network traffic and minimise method calls across the network.

    LDAP DAO Classes

    The BaseLdapDAO class is responsible for common functionality,
    including methods for creating, updating and removing entries. Functionality
    specific to Users, Groups and Roles are in the LdapUser, LdapGroup and LdapRole
    classes. There are detailed tutorials on JNDI elsewhere on the web (see [5]
    for example), so we will just give a brief overview of some common tasks. For
    example, inserting an entry into the LDAP repository with JNDI is done with
    the createSubcontext method. Here, we call the create method on the LdapUserDAO
    class, passing in the UserTO object we want to insert. This calls the create
    method on the superclass, BaseLdapDAO; to determine the details of the entity
    to be added, BaseLdapDAO then calls the getDN and getAttributes methods on the
    LdapUserDAO:

    
      createSubcontext(getDN(entity), getAttributes(entity)); 
      

    Similarly, entities are removed with the DirectoryContext.destroySubcontext method, passing
    in the Distinguished Name (DN) of the entity to be removed:

    
      ctx.destroySubcontext(getDN(entity)); 
      

    An entry.s attributes can be modified by creating an array of ModificationItems
    containing the Attribute to be modified:

    
      ModificationItem[] mods = new ModificationItem[1]; 
      Attribute mod = new BasicAttribute("userPassword", newPass); 
      mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod); 
      ... 
      getInitialLdapContext().modifyAttributes(dn, mods); 
      

    This is a just
    a brief overview of some of the functionality possible when using JNDI with
    LDAP; the example classes include some more sample code that is not described
    here.

    Summary

    To summarise, we first covered a possible design for an LDAP
    schema that would allow implementation of Role Based Access Control; this schema
    was comprised of Users, Groups and Roles. We then covered the configuration
    of JBoss security, including building custom login modules. Finally, we saw
    a design for the persistence layer of a user information repository, using the
    Abstract Factory, Session Facade, and Data Access Objects patterns, and looked
    briefly at some JNDI code for adding, removing and updating entries in LDAP.

    References

    Downloads


    Download source – 43 Kb

    Latest Posts

    Related Stories