Monday Feb 12, 2007

The facts on OpenDS ACID properties and LDAP transactions

Readers left numerous comments on last week's post (LDAP vs relational databases) that need further elaboration. To that end, Neil Wilson (OpenDS architect) just posted an excellent summary describing OpenDS ACID properties and our plans to support LDAP transactions. Thanks Neil!

Thursday Feb 08, 2007

OpenDS <> Roller integration

A recent experiment had me working out how to LDAP enable Roller authentication. With Dave's (Roller team) help the configuration below has been proven to work. You may find it kludgey in that 1) after registration the user must close and re-open the browser due to a Roller bug and 2) enabling SSO doesn't have anything to do with enabling LDAP authentication, but it works.

Prepare or install a directory server

For starters if you don't have a directory (LDAP) server available get one. You will need modify rights as all Roller users must belong to the group “register” to successfully login. Properly adding LDAP users/groups can be confusing. Review the first section of my previous entry on OpenDS configuration and use this sample LDIF to add a properly defined Roller user.

Configure Roller

Enable single sign on

Open the roller.properties file and change the property “users.sso.enabled” from “false” to “true”; i.e., “users.sso.enabled=true”

Swap out the internal Roller authentication provider and replace with LDAP

Open the security.xml file and edit as follows: Uncomment the elements beginning with “<!-- Sample LDAP/RollerDB hybrid security configuration -->” Secure the user registration page by adding the line " /roller-ui/user.do\*=register" to the filterInvocationInterceptor bean value; i.e.,

   <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
       <property name="authenticationManager" ref="authenticationManager"/>
       <property name="accessDecisionManager" ref="accessDecisionManager"/>
        <property name="objectDefinitionSource">
           <value>
               PATTERN_TYPE_APACHE_ANT
               /roller-ui/login-redirect.jsp=admin,editor
               /roller-ui/yourProfile\*\*=admin,editor
               /roller-ui/createWebsite\*\*=admin,editor
               /roller-ui/yourWebsites\*\*=admin,editor
               /roller-ui/authoring/\*\*=admin,editor
               /roller-ui/admin/\*\*=admin
               /roller-ui/user.do\*=register
               /rewrite-status\*=admin
           </value>
       </property>
   </bean>
Securing the registration page enables Roller to prompt for LDAP credentials and use the authenticated user information to pre-populate the user registration form. Note: the user must belong to a LDAP group named “register”.


Comment out the DAO authentication provider (Roller) and replace with the LDAP provider; i.e.,
   <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
       <property name="providers">
           <list>
               <!-- <ref local="daoAuthenticationProvider"/> -->
               <ref local="ldapAuthProvider"/>
               <ref local="anonymousAuthenticationProvider"/>
               <!-- rememberMeAuthenticationProvider added programmatically -->
           </list>
       </property>
   </bean>

Point the authentication provider to your OpenDS instance. Note that the configuration below is specific to your OpenDS installation. For example:
 
  <bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
     <constructor-arg value="ldap://localhost:1389/dc=example,dc=com"/>
     <property name="managerDn">
       <value>cn=Directory Manager</value>
     </property>
     <property name="managerPassword">
       <value>password</value>
     </property>
   </bean>
  • constructor-arg = the LDAP URL to the OpenDS server and base suffix
  • managerDN = (Optional) If you do not allow anonymous search specify a user capable of searching the user/group tree
  • managerPassword = (Optional) The managerDN password


Specify the user search criteria. The attribute “uid” is a typical unique identifier. Note the token '{0}' is replaced with the username entered on the Roller login form.

   <bean id="ldapUserSearch"
class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
     <constructor-arg index="0">
       <value></value>
     </constructor-arg>
     <constructor-arg index="1">
       <value>uid={0}</value>
     </constructor-arg>
     <constructor-arg index="2">
       <ref local="initialDirContextFactory" />
     </constructor-arg>
     <property name="searchSubtree">
       <value>true</value>
     </property>
   </bean>


You may find my security.xml file and roller.properties file helpful as a guide. Good luck.

Wednesday Feb 07, 2007

LDAP vs relational database

Typically, when I ask enterprise developers where they'll store application data the response is immediate, a relational database. The directory server isn't even an afterthought. There's a long laundry list of reasons why: no one gets fired for choosing Oracle, its already there, familiarity with SQL, plethora of RDBMS tools, LDAP writes are slow, JNDI interface - are you kidding?!... While all of these are legitimate retorts, LDAP, and in particular OpenDS, bring with it clear advantages for certain classes of applications.

A short list of OpenDS differentiators include:

It's a database! LDAP isn't just a dumping ground for user profile data, it's a full-fledged hierarchical database. If your data is modeled in a parent-child fashion, not unusual in a XML world, then LDAP might be a better fit. LDAP data modeling is very different than relational modeling though at a simplistic level you might map tables to entries and columns to attributes.

Change schema over protocol: Schema is modifiable over the standard LDAP protocol - no vendor specific tools or syntax required. Building an application that requires frequent and/or unpredictable schema changes is difficult with relational databases and, I've found, often handled by creating generic trees (parent child relationships). Highly, dynamic “Web 2.0'ish” applications come to mind; e.g., generic Atom server where arbitrary schema changes are expected.

Out-of-the-box synchronization: Synchronizing directory databases is a standard feature and requires minimal administration. It just works...at least that's the idea. A very cool feature for H/A and solving data adjacency issues.

Read/write performance parity: At the moment I'm unable to release specifics on OpenDS performance, but those of you with memories of slow write performance are in for a pleasant surprise

Embedded mode: OpenDS need not run in a separate process, rather it can be directly embedded in your application. This is especially useful for cases where one may need the benefits of a LDAP (the other bullets) database with zero external administration.

Scalability: OpenDS, stripped down, runs on PDA's and scales to telcos.

Extensibility: Aside from standard plugin extensions there are numerous hooks within OpenDS that are easily modified by a Java developer. OpenDS is 100% Java after-all. A deployer is free to customize protocol handlers, client connection handlers, configuration, monitors, loggers, and even the backend (we use Berkley DB).

Internationalization over protocol: Fetching language specific directory attributes is easily done over protocol using LDAP attribute options. For example, you may search for the English specific “description” attribute by searching “description;lang-en: software products” or the German version “description;lang-de: Softwareprodukte”

Matching rules: Directory servers provide a flexible facility for determining attribute value equality. Typical equality matching is supported out-of-the-box; e.g., case ignore, greater/less than, etc. The differentiating feature is the ability to write your own matching rules for specific attributes. An interesting application of this is fuzzy matching rules; instead of implementing approximate matches in your application logic this can be done within the data repository. For example, if I search for all Atom entries with the an attribute named “category” and the value “foo” I may have a matching rule that finds and return all entries that contain category values with “bar” and “baz” as well.

More to follow...

Tuesday Jan 30, 2007

OpenDS news feed

OpenDS news is now available via RSS. Check it out at https://opends.dev.java.net/servlets/ProjectRSS?type=news.

Friday Jan 26, 2007

Glassfish OpenDS integration

There's little need to explain why one would want to integrate application authentication with a centralized LDAP server so I'll get right to business. For test purposes I'm using the OpenDS directory and the Glassfish application server. In case you haven't heard, OpenDS is a 100% pure Java, open source, directory server specifically designed for large deployments where high performance is critical and ease of management is required. And, of course, Glassfish is a JEE 5 compliant application server.

To get started, you'll first need to install an OpenDS instance. Installation is made quick and easy via the "Quick Setup" Web Start installer and step-by-step instructions. During the installation process note the installation path, LDAP listener port, administrative User DN/password and the Directory Base DN as you'll need these properties to configure the Glassfish realm.

Next, load sample users and groups into OpenDS. The sample data is provided in LDIF format as it is the quickest and easiest method to import data into OpenDS. For test purposes I defined a single group, "webappgroup", with 1 member "treydrake". An additional user "noaccess" is defined to verify the solution works. Note that the web.xml, defined at the bottom of the page, grants access only to members of the group "webappgroup".

# add group
dn: ou=Groups,dc=example,dc=com
changetype: add
ou: Groups
description: Group ou
objectClass: top
objectClass: organizationalUnit

# add people ou
dn: ou=People,dc=example,dc=com
changetype: add
ou: People
description: People
objectClass: top
objectClass: organizationalUnit

# add an authorized user (belongs to the group webappgroup)
dn: uid=treydrake,ou=People,dc=example,dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: treydrake
cn: Trey Drake
sn: Drake
givenName: Trey
userPassword: password

# unauthorized user
dn: uid=noaccess,ou=People,dc=example,dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: noaccess
cn: No Access
sn: access
givenName: no
userPassword: noaccess

# add user to the webapp group
dn: cn=webappgroup,ou=Groups,dc=example,dc=com
changetype: add
objectClass: top
objectClass: groupOfUniqueNames
uniqueMember: uid=treydrake,ou=People,dc=example,dc=com
cn: webappgroup

Use the ldapmodify tool included in the OpenDS install to import the LDIF file as shown below. In the following examples I assume OpenDS is installed locally, the port is 1389, and the administrative user/password is the default "Directory Manager"/"password". If you customized the install and/or need to use more advanced options type "ldapmodify -H" to get complete usage info. For example:

~/OpenDS/bin treydrake$ ~/OpenDS/bin/ldapmodify -p 1389 -a -D "cn=Directory Manager" -w password -f ~/dev/opendsglassfishrealm-wksp/opendsauthtest/ldifs/data.ldif

Use ldapsearch to verify that the new user 'treydrake' can successfully authenticate to OpenDS and that the user is a member of the webappgroup. For example:

~/OpenDS/bin treydrake$ ~/OpenDS/bin/ldapsearch -p 1389 -D "uid=treydrake,ou=People,dc=example,dc=com" -w password -b ou=Groups,dc=example,dc=com objectclass=groupofuniquenames

Next, add an OpenDS realm to the Glassfish application server via the Glassfish console. Login to the console; e.g., http://localhost:4848 and navigate to Configuration -> Security -> Realms and click the "New" button. See the screen shot below for property settings:

The Glassfish LDAP realm requires the following properties:
  • directory - LDAP URL to the OpenDS instance; e.g., ldap://localhost:1389
  • base-dn - Base Distinguished Name (DN) for the location of user data, which can be at any level above the user data, since a tree scope search is performed. The smaller the search tree, the better the performance.
  • jaas-context - Must be "ldapRealm".
The following properties are optional:
  • search-filter - Search filter to use to find the user. The default value is uid=%s (%s expands to the subject name).
  • group-base-dn - Base DN for the location of group data. Same as the base-dn but it can be tuned if necessary.
  • group-search-filter - Search filter to find group memberships for the user. Defaults to uniquemember=%d (%d expands to the user element DN).
  • group-target - LDAP attribute name that contains group name entries. Defaults to CN.
  • search-bind-dn - Optional DN used to authenticate to the directory for performing the search-filter lookup. Only required for directories that do not allow anonymous search.
  • search-bind-password - LDAP password for the DN given in search-bind-dn.

Next, configure the web.xml and sun-web.xml descriptors to authenticate using the OpenDS realm by adding the security-constraint and login-config elements to your application's web.xml file and the role <-> group mapping in sun-web.xml. See samples below: web.xml

<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xsi="http://www.w3.org/2001/XMLSchema-instance"
schemalocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

<welcome-file-list>
<welcome-file>Index.jsp</welcome-file>
</welcome-file-list>

<!-- grant access to all users that possess the role
'secure' and deny all others -->

<security-constraint>
<web-resource-collection>
<web-resource-name>opendsauthtest</web-resource-name>
<url-pattern>/\*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>secure</role-name>
</auth-constraint>
</security-constraint>

<!-- declare the app uses FORM based authentication
using your newly created OpenDS realm -->

<login-config>
<auth-method>FORM</auth-method>
<realm-name>OpenDS</realm-name>
<form-login-config>
<form-login-page>/WEB-INF/jsp/Login.jsp</form-login-page>
<form-error-page>/WEB-INF/jsp/LoginError.jsp</form-error-page>
</form-login-config>
</login-config>

</web-app>
The Glassfish specific descriptor (sun-web.xml) maps the web application role defined in the web.xml descriptor to a LDAP group. sun-web.xml
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE sun-web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Application Server 8.1 Servlet 2.4//EN"
"http://www.sun.com/software/appserver/dtds/sun-web-app_2_4-1.dtd">
<sun-web-app>
<security-role-mapping>
<role-name>secure</role-name>
<group-name>webappgroup</group-name>
</security-role-mapping>
</sun-web-app>

Finally, deploy your application and attempt to login using the username "treydrake" and password "password". Authenticating as "noaccess", or any other user for that matter, should fail. For convenience sake, I've uploaded a web application that's configured using the above instructions. Good luck.

About

treydrake

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today