C# LDAP performance

2019-04-13 03:23发布

Where I work, we have two modes of authentication:

CAS is the primary method, but it is often unreliable at peak traffic times and so we have been using LDAP as a fallback mode for when we notice that CAS is down. Previously, we were using PHP for doing our LDAP fallback and got reasonable performance. There wasn't a noticeable delay during login other than the expected network lag times. A login took probably ~250-500ms to complete using LDAP.

Now, we are making a new system and have chosen ASP.NET MVC4 as the platform rather than PHP and I am tasked with trying to get this fallback working again. I have been pulling my hair out for about 6 hours now trying different things over and over again, getting the same result (perhaps I am insane). I have finally managed to connect to LDAP, authenticate the user, and get their attributes from LDAP. However, the query consistently takes 4.5 seconds to complete no matter what method I try.

This is very surprising to me seeing as the PHP version was able to do nearly the same thing in 1/8th the time and it would seem that the .NET framework has excellent support for LDAP/ActiveDirectory. Am I doing something incredibly horribly wrong?

Here are the guts of my function as it stands now (this one is the latest iteration that manages to do everything in one 4.5 second query):

public Models.CASAttributes Authenticate(string username, string pwd)
{
    string uid = string.Format("uid={0},ou=People,o=byu.edu", username);

    LdapDirectoryIdentifier identifier = new LdapDirectoryIdentifier("ldap.byu.edu", 636, false, false);

    try
    {
        using (LdapConnection connection = new LdapConnection(identifier))
        {
            connection.Credential = new NetworkCredential(uid, pwd);
            connection.AuthType = AuthType.Basic;
            connection.SessionOptions.SecureSocketLayer = true;
            connection.SessionOptions.ProtocolVersion = 3;

            string filter = "(uid=" + username + ")";
            SearchRequest request = new SearchRequest("ou=People,o=byu.edu", filter, SearchScope.Subtree);
            Stopwatch sw = Stopwatch.StartNew();
            SearchResponse response = connection.SendRequest(request) as SearchResponse;
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds);
            foreach (SearchResultEntry entry in response.Entries)
            {
                Debug.WriteLine(entry.DistinguishedName);
                foreach (System.Collections.DictionaryEntry attribute in entry.Attributes)
                {
                    Debug.WriteLine(attribute.Key + " " + attribute.Value.GetType().ToString());
                }
                Debug.WriteLine("");
            }
        }
    }
    catch
    {
        Debugger.Break();
    }

    Debugger.Break();
    return null; //debug
}

The PHP version of this follows this sequence:

  1. Bind anonymously and look up the user information using a basedn and cn
  2. Bind again using the username and password of the user to see if they are authentic

It does two binds (connects?) in 1/8th the time it takes the .NET version to do one! Its this sort of thing that makes me thing I am missing something.

I have tried methods based on the following sites:

EDIT:

Using wireshark, I saw that the following requests are made:

  1. bindRequest passing along my uid (delta 0.7ms)
  2. bindResponse success (delta 2ms)
  3. searchRequest "ou=People,o=byu.edu" wholdSubtree (delta 0.2ms)
  4. searchResEntry "uid=my uid,ou=People,o=byu.edu" | searchResDone success 1 result (delta 10.8ms)
  5. unbindRequest (delta 55.7ms)

Clearly, the overhead is coming from .NET and not from the requests. These don't add up to 4.5 seconds in any way, shape, or form.

2条回答
The star\"
2楼-- · 2019-04-13 04:02

I think you're definitely on the right track using System.DirectoryServices for this, so you may just need to tweak your search request a bit.

You're only looking to get one result back here, correct? Set your size accordingly :

request.SizeLimit = 1;

This is a tricky one, but make also sure you're suppressing referral binds as well. You'll want to set this before you call connection.SendRequest(request) :

//Setting the DomainScope will suppress referral binds from occurring during the search
SearchOptionsControl SuppressReferrals = new SearchOptionsControl(SearchOption.DomainScope);
request.Controls.Add(SuppressReferrals);
查看更多
Evening l夕情丶
3楼-- · 2019-04-13 04:12

ldap.byu.edu sure looks like a fully qualified DNS host name. You should change your LdapDirectoryIdentifier constructor to new LdapDirectoryIdentifier("ldap.byu.edu", 636, true, false).

查看更多
登录 后发表回答