[Transferred from http://blog-gabriel.mongefranco.com; originally published on June 22, 2008]
Introduction
The new JasperServer 3 business intelligence (BI) and report server was released this month. And the first thing I attempted right after installing it was setting up Active Directory integration. As it turns out, AD integration is not an easy project, especially due to the lack of documentation. After almost a week of intensive research, I finally figured out the essentials to get AD authentication working. AD authorization, on the other hand, is still far beyond my reach. In this post, I briefly document my findings to[!]st others in setting up AD authentication with JasperServer 3.
Background
The first important point to notice is that JasperServer authentication is based on Spring (formerly Acegis). As such, LDAP authentication comes built out of the box. Of course, it still requires some tweaking. All configuration is performed inside of two XML files under JasperServer's WEB-INF directory.
JasperServer makes use of roles to provide access. The default roles are ROLE_ADMIN (used for administrators), ROLE_USER (used for regular users), and ROLE_ANONYMOUS (not really used at all). After a user is authenticated, his/her roles are checked to determine what resources are accessible. In the case of LDAP, users are automatically added as new external users upon a successful authentication. Moreover, they are[!]gned the ROLE_USER role by default, which might be undesirable.
In my test server, all the users are located under an organizational unit called BlogUsers. Thus, the complete path to my user ID would be: cn=gabriel,ou=BlogUsers,dc=mongefranco,dc=com. Moreover, I have different security groups setup under a different OU. My group would be: cn=Developers,ou=DepartmentGroups,ou=Groups,dc=mongefranco,dc=com.
Authentication Configuration
The first file to configure is ApplicationContext-security.xml, which is located in the WEB-INF directory. In the Authentication section, there is a bean called "authenticationManager." This bean contains a list of authentication providers, that is, plug-ins that provide access to different protocols. When a user hits login on the home page, his credentials are authenticated by each of these providers, top to bottom, until a positive match is found. If none of the providers returns a positive match, the user is denied access.
Since Active Directory uses the LDAP protocol, I enabled "ldapAuthenticationProvider." I also left "daoAuthenticationProvider" enabled so I could create local accounts that are no in AD, such as the jasperadmin administrative account. Also, for added security, I disabled the anonymousAuthenticationProvider." Thus, my authenticationManager bean looks like this:
<!-- ======================== AUTHENTICATION ======================= -->
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="ldapAuthenticationProvider"/>
<ref local="daoAuthenticationProvider"/>
<!--ref local="anonymousAuthenticationProvider"/-->
<!--ref local="jaasAuthenticationProvider"/-->
</list>
</property>
</bean>
The next bean is the "initialDirContextFactory" which sets up LDAP access for Spring. The first constructor-arg should contain only the Active Directory domain controller address, port number (usually 389), and the DC portion of the DN (do not include organizational units here!). The next two properties, "managerDN" and "managerPassword," are the user name and password of the account used to query Active Directory. In my test system, this is a service account used exclusively to query AD. The complete bean looks as follows:
<bean id="initialDirContextFactory" class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://ADSERVER:389/dc=mongefranco,dc=com"/>
<property name="managerDn"><value>svc_ldap@mongefranco.com</value></property>
<property name="managerPassword"><value>password123!</value></property>
</bean>
After a user clicks login on the home page, Spring first attempts to connect to AD using the settings above. After a successful connection, in re-binds using the user-supplied user name and password. In order to do this, it needs to know where and how to find the user. That is where the "userSearch" bean comes in. Composed of 3 constructors, it defines what attribute in AD contains the user name and also how to find it. In my system, the attribute is CN, so I put "cn={0}" where "{0}" is replaced by the user name entered by the user. The "searchSubtree" option will allow Spring to dig into sub-groups.
<bean id="userSearch" class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0">
<value></value>
</constructor-arg>
<constructor-arg index="1">
<value>cn={0}</value>
</constructor-arg>
<constructor-arg index="2">
<ref local="initialDirContextFactory" />
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
The next bean, ldapAuthenticationProvider, is a bit sensitive so you must be extra careful. It contains two other beans. The first has a "userDnPatterns" property used to find a user in the directory. After Spring successfully binds to AD using the user name and password provided, it checks the userDnPatterns to find the user in the directory. If it cannot find the user, it immediately denies access. Since in my system all users are contained in the BlogUsers OU, I constructed the parameters as follows:
<constructor-arg>
<bean class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<property name="userDnPatterns"><list>
<value>cn={0},ou=BlogUsers</value>
</list></property>
</bean>
</constructor-arg>
The next bean is used for authorization and to map a user's AD group or OU to a JasperServer role. For the love of God, I could not get this to work. So if you know how.... please let me know. :) The only thing I was able to configure was a defaultRole property, which causes JasperServer to automatically[!]gn this role to a user upon a first time successful login.
<constructor-arg>
<bean class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg index="0"><ref local="initialDirContextFactory"/></constructor-arg>
<constructor-arg index="1"><value>ou=DepartmentGroups,ou=Groups</value></constructor-arg>
<property name="groupRoleAttribute"><value>cn</value></property>
<property name="groupSearchFilter"><value>((member={1})(cn=*))</value></property>
<property name="searchSubtree"><value>true</value></property>
<property name="rolePrefix"><value></value></property>
<property name="defaultRole"><value>ROLE_ANONYMOUS</value></property>
<property name="convertToUpperCase"><value>true</value></property>
</bean>
</constructor-arg>
In addition to the defaultRole setup above, JasperServer also[!]gns a "ROLE_USER" which may be undesirable. At least in my case, I would not want every user to stumble upon the JasperServer URL, login and have access to all reports. So after a couple of days of unsuccessful research, I simply searched for all references to "ROLE_USER" in all the XML files on the server. Eventually, I discovered that this second default role is configured in the ApplicationContext.xml file. After changing it to ROLE_ANONYMOUS, I denied access to every resource (including the root directory) to this role. That way, even though anyone with an AD account can login, he/she will get an "Access Denied" message.
Hopefully, my short experience with AD, LDAP, Spring and JasperServer will help someone out there. Believe me, the lack of documentation in the subject is astonishing, so I am hoping this will help the open source community.