星期二, 二月 24, 2004

如何在JAAS框架下使用JBOSS和LDAP实现认证(Authenticate)用户的步骤.

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: jbloggs@sample.com
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:
Password:
<input 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:
Create a login page
Set up the web.xml file
Set up the login-config.xml file
Modify the jboss-web.xml and jboss.xml files to include the security domain
Place any custom login modules in the lib directory
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
The OpenLDAP Project (http://www.openldap.org/)

OpenLdap for Windows (http://www.fivesight.com/downloads/openldap.asp)

Sun LDAP tutorial (http://java.sun.com/products/jndi/tutorial/ldap/)

Sun JNDI tutorial (http://java.sun.com/products/jndi/tutorial/)

JLDAP project (http://www.openldap.org/jldap/)

Netscape Directory API (http://www.mozilla.org/directory/javasdk.html)

Core J2EE Patterns Catalogue (http://java.sun.com/blueprints/corej2eepatterns/Patterns/index.html)

The Jakarta Struts Framework (http://jakarta.apache.org/struts/)

JBoss Group (http://www.jboss.org/)

Apache Ant (http://ant.apache.org/)

Downloads