Microsoft Active Directory Authentication with Java using Spring Security 3.1.0

Overview

The directory service is a software service stores, organizes, and provides query functionalities of a directory. Almost a century ago, the telecommunication companies create and manage phonebook directories. With their expertise, the telecomm introduced the X.500 Directory Access Protocol (DAP) specification. LDAP, stands for Lightweight Directory Access Protocol, was created to be a lightweight version of X.500 DAP protocol. The modern standalone LDAP server usually supports both X.500 DAP and LDAP protocol out of the box.

On the other hand, Microsoft introduced Active Directory (a.k.a. AD), which is a directory service uses LDAP version 2 and 3, Kerberos, and DNS. The Active Directory contains more attributes than it’s Linux siblings LDAP service. As a result, integration with Active Directory Lightweight Directory Service (AD-LDS) could be tricky sometimes.

JAAS

JAAS, or Java Authentication and Authorization Service, is a Java implementation of PAM (Pluggable Authentication Module) framework. JAAS is first introduced into J2SE 1.3 and officially integrated into the JDK in 1.4. Developers should be able to use JAAS to authenticate against with the Microsoft Active Directory. Since JAAS has been in the field for awhile, there are a lot of articles to discuss how to use JAAS with Microsoft Active Directory. I am not going to cover that in this article.

Spring Security

The older version of Spring Security framework has been already able to authenticate with LDAP and which some extra works, the framework can be able to authenticate against with Microsoft AD as well. Since Spring Security 3.1.0, SpringSource adds ActiveDirectoryLdapAuthenticationProvider besides the existing LdapAuthenticationProvider to have better integration with Microsoft Active Directory authentication and authorization.

In order to use Spring Security, please add the following into your project Maven pom.xml.

  
  <properties>
    <spring.security.version>3.1.0.RELEASE</spring.security.version>
  <properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-core</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-taglibs</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-ldap</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
  </dependencies>

Please note, Spring Security 3.1.0.RELEASE is still using Spring Framework 3.0.6.RELEASE. Beware of Spring Framework dependencies collision if you are using newer or older version of Spring framework!!! You can use <exclusions> to exclude the dependencies. Next step, add the spring-security-context.xml file into src/main/webapp/WEB-INF/spring. Assuming you are developing using Spring MVC and using Spring Template Project in Eclipse to create your webapp project, modify src/main/webapp/WEB-INF/spring/root-context.xml to include our Spring Security configuration file as following:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <import resource="spring-security-context.xml"/>

</beans>

The src/main/webapp/WEB-INF/spring/spring-security-context.xml file looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
             xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
  <security:http pattern="/css/**" security="none" />
  <security:http pattern="/images/**" security="none" />
  <security:http pattern="/js/**" security="none" />
  <security:http pattern="/favicon.ico" security="none" />
  <security:http pattern="/webapp/login" security="none" />

  <security:authentication-manager>
    <authentication-provider ref="ldapActiveDirectoryAuthProvider" />
  </security:authentication-manager>

  <beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
    <beans:constructor-arg value="iceycake.com" />
    <beans:constructor-arg value="ldap://openldap.iceycake.com:389" />
    <beans:property name="authoritiesMapper" ref="grantedAuthoritiesMapper" />
    <beans:property name="useAuthenticationRequestCredentials" value="true" />
    <beans:property name="convertSubErrorCodesToExceptions" value="true" />
  </beans:bean>

  <beans:bean id="grantedAuthoritiesMapper" class="com.iceycake.security.ActiveDirectoryGrantedAuthoritiesMapper"/>

  <security:http>
    <security:form-login login-page="/webapp/login" default-target-url="/webapp/" authentication-failure-url="/webapp/login?login_error=true" />
    <security:http-basic />
    <security:logout logout-url="/webapp/login" />
    <security:session-management invalid-session-url="/webapp/login" />
    <security:intercept-url pattern="/**" access='ROLE_USER' />
  </security:http>
</beans:beans>

To authenticate with Microsoft Active Directory, using ActiveDirectoryLdapAuthenticationProvider instead of LdapAuthenticationProvider for the security:authentication-manager. The constructor of ActiveDirectoryLdapAuthenticationProvider takes 2 arguments in the constructor: domain and url. The first argument is domain, which represents the domain of the Active Directory. The value of domain will be automatically attached to the username if the user didn’t provide the domain name during the login process. For example, if my username is andy, Spring Security will submit andy@iceycake.com to the Active Directory in my example. The second argument is url, which is the host and port of the Microsoft Active Directory. In my example, the value is ldap://openldap.iceycake.com:389.

I have set useAuthenticationRequestCredentials to true so Spring Security will use my supplied password as the credentials in the successful authentication token. In addition, I have set convertSubErrorCodesToExceptions to true so any AD errors will be converted into Exceptions. I have also provided my own authorities mapper to map the AD authorities into my application’s user role:

 
public enum IceycakeAuthority implements GrantedAuthority {
    ROLE_USER, ROLE_ADMIN;

    @Override
    public String getAuthority() {
        return name();
    }
}
 
public class ActiveDirectoryGrantedAuthoritiesMapper implements GrantedAuthoritiesMapper {
    private static final String ROLE_NORMAL_USER = "Normal User";
    private static final String ROLE_ADMIN = "Administrator";

    public ActiveDirectoryGrantedAuthoritiesMapper() {        
    }

    @Override
    public Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
        Set<IceycakeAuthority> roles = EnumSet.noneOf(IceycakeAuthority.class);

        for (GrantedAuthority authority : authorities) {
            if (ROLE_NORMAL_USER.equalsIgnoreCase(authority.getAuthority())) {
                roles.add(IceycakeAuthority.ROLE_USER);
            } else if (ROLE_ADMIN.equalsIgnoreCase(authority.getAuthority())) {
                roles.add(IceycakeAuthority.ROLE_ADMIN);
            }
        }

        return roles;
    }
}

You can use RDBMS or properties files to perform the mapping. The results will be used in spring-security-context.xml:

  
  <security:http>
    <security:form-login login-page="/webapp/login" default-target-url="/webapp/" authentication-failure-url="/webapp/login?login_error=true" />
    <security:http-basic />
    <security:logout logout-url="/webapp/login" />
    <security:session-management invalid-session-url="/webapp/login" />
    <security:intercept-url pattern="/**" access='ROLE_USER' />
  </security:http>

Conclusion

Spring Security 3.1.0 makes authentication and authorization with Microsoft Active Directory extremely simple with Spring MVC framework. All the filters are provided and all you need to do is providing AD configuration and write some codes for the user role mappings.

Filed Under: Software DevelopmentTutorial

Tags:

About the Author: Andy H. Chan has years of enterprise software development and architecture experience. He is also the co-author of the book Pro Spring Integration. He can be reached Twitter @iceycake.

RSSComments (7)

Leave a Reply | Trackback URL

  1. Tirthankar says:

    Did not find the class CityGridAuthority.

  2. Mamster says:

    Interesting article Andy. I just referred to it for someone else. They may need SSO as well.

  3. sneha says:

    The password in the AD directly are stored using encryption, I tried using password-encoder but it gives a listener-start error…
    below is the code i have written

    please help

  4. Syed says:

    Hi Andy,
    I have a spring (security) based web app. I would like to be able to check for client certificates and authenticate them against the MS AD CA server running on IIS. My web app will be running on centOS on tomcat 6.

    Im currently looking at waffle and tomcat Spnego but as I have little experience connecting a web app on tomcat that will auth with MS CA server on IIS Im finding it a little tricky.

    Just calling out to people in the know for a little help, advice and guidance.

    Regards
    Syed Shah

Leave a Reply

%d bloggers like this: