简体   繁体   中英

FastBind for authentication against Active Directory using Spring LDAP

I have a Spring MVC application (using 3.0.5 version), and need to bind to Active Directory using Spring LDAP for simply and only authenticating user's credentials. The application is hosted on a Linux server, so I need a cross-platform solution. And the application does not use Spring Security.

What is an effective way to implement user authentication in this setup? Active Directory supports FastBind control (id= 1.2.840.113556.1.4.1781 ), so I would like to leverage that since all I need is validation of the input credentials and need no other information back from AD.

Thanks!

Update (7/16/2012) : I will continue to update my question with details of the resolution.

Based on the answer from @ig0774 I wrote the following connection control class:

package com.company.authentication;

import javax.naming.ldap.Control;

public class FastBindConnectionControl implements Control {

    @Override
    public String getID() {
        return "1.2.840.113556.1.4.1781";
    }

    @Override
    public boolean isCritical() {
        return true;
    }

    @Override
    public byte[] getEncodedValue() {
        return null;
    }
}

Then, I extended AbstractContextSource , using the FastBind connection-control class:

package com.company.authentication;

import java.util.Hashtable;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import org.springframework.ldap.core.support.AbstractContextSource;

public class FastBindActiveDirectoryContextSource extends AbstractContextSource {

    @Override
    protected DirContext getDirContextInstance(Hashtable env) throws NamingException {
        return new InitialLdapContext(env, new Control[] { new FastBindConnectionControl() });
    }
}

Finally, a service class to encapsulate the authentication mechanism:

package com.company.authentication;

import javax.naming.AuthenticationException;
import javax.naming.directory.DirContext;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.support.LdapUtils;

public class ActiveDirectoryAuthService implements IAuthenticate {

    private ContextSource contextSource;
    public void setContextSource(ContextSource contextSource) {
        this.contextSource = contextSource;
    }

    @Override
    public boolean authenticate(final String login, String password) {
        try {
            DirContext ctx = contextSource.getContext(login, password);
            LdapUtils.closeContext(ctx);
            return true;
        }
        catch (Exception e) {
            return false;
        }
    }
}

In my Spring application context configuration file, I added the following:

<bean id="ADContextSource" class="com.company.authentication.FastBindActiveDirectoryContextSource">
    <property name="url" value="ldaps://x.x.x.x:636" />
</bean>

<bean id="userAuthenticationService" class="com.company.authentication.ActiveDirectoryAuthService">
    <property name="contextSource" ref="ADContextSource" />
</bean>

Finally, userAuthenticationService bean is injected into the client class, say a login controller.

package com.company.web;

import com.company.authentication;

@Controller
public class LoginController {

    @Autowired
    private IAuthenticate userAuthenticationService;

    public String authenticateUser(String login, String password) {
      if (this.userAuthenticationService.authenticate(login, password)) {
          return "welcome";
      }
      else {
        return "login";
      }
    }
}

Implementing the FastBind control in JNDI is pretty straight-forward as discussed in this OTN forum post .

Basically, you create a new Control class for the FastBind control:

class FastBindConnectionControl implements Control {
    public byte[] getEncodedValue() {
            return null;
    }
    public String getID() {
        return "1.2.840.113556.1.4.1781";
    }
    public boolean isCritical() {
        return true;
    }
}

And then use that to create your ldap context (error handling and everything else ignored):

LdapContext ctx = new InitialLdapContext(env, new Control[] {new FastBindConnectionControl()});

Ideally, this would be easy to plug into Spring-LDAP which is a wrapper around the JNDI API for LDAP; however, it looks as though the interface for the built-in LdapContextSource does not accept a parameter to deal with connection controls, so you'll apparently need to create your own sub-class of AbstractContextSource to handle that, which looks like it should be straight-forward enough:

class FastBindLdapContextSource extends AbstractContextSource {
    protected DirContext getDirContextInstance(Hashtable env) {
        return new InitialLdapContext(env, new Control[] {new FastBindConnectionControl()});
    }
}

You would then just need to replace your current LdapContextSource with an instance of FastBindLdapContextSource .

Note, however, that this context source can only be used for BIND operations. As noted in the MSDN document I linked to in a comment to Terry Gardner:

Only simple binds are accepted on a connection in this mode. Because no group evaluation is done the connection is always handled as if no bind had occurred for the purposes of all other LDAP operations.

Which means that you're potentially looking at maintaining two types of LDAP contexts, one to do binds and one to actually perform any look-ups you may need to do.


Looking at the source code for LdapTemplate , I see the authenticate method looks like it does a bit more than just a simple bind. More specifically, it does a search for the user and then attempts the bind. Since, if you're using a context with FastBind enabled you're unlikely to be able to perform a search (usually AD doesn't permit searches on anonymous connections). Basically, that means you probably have to avoid LdapTemplate .

However, assuming you get a reference to your ADContextSource bean, it should be simple enough to do something like

boolean authenticate(String username, String password) {
    try {
        DirContext ctx = contextSource.getContext(username, password);
        LdapUtils.closeContext(ctx);
        return true;
    } catch (Exception e) {
        // note: this means an exception was thrown by #getContext() above
        return false;
    }
}

Which fairly closely mimics what LdapTemplate would do anyways (the only things that are missing is the AuthenticatedLdapEntryContextCallback , which isn't of any value in this scenario, and the AuthenticationErrorCallback , which could easily be added in if you want that behavior).

Since the FastBind only "checks the user's credentials" and does not perform group determinations, it might be best to simply transmit a BIND request to the server using the distinguished name and credentials. Any simple BIND request should be transmitted over a secure connection. A response with a result code of 0 (zero) indicates that:

  1. The server is listening and responding
  2. The distinguished name exists and the credentials match those stored in the server database
  3. The LDAP client has sufficient access permission to authenticate the session on the server

The UnboundID LDAP SDK is the best Java implementation for interacting with directory servers

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM