C# 如何通过 LDAP + SSL 验证 Active Directory 凭据?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10850860/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 15:31:05  来源:igfitidea点击:

How do I validate Active Directory creds over LDAP + SSL?

c#.netactive-directoryldapdirectoryservices

提问by Nate Sauber

I'm trying to use the .NET 3.5 System.DirectoryServices.AccountManagementnamespace to validate user credentials against our Active Directory LDAP server over an SSL encrypted LDAP connection. Here's the sample code:

我正在尝试使用 .NET 3.5System.DirectoryServices.AccountManagement命名空间通过 SSL 加密的 LDAP 连接针对我们的 Active Directory LDAP 服务器验证用户凭据。这是示例代码:

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:389", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
    return pc.ValidateCredentials(_username, _password);
}

This code works fine over unsecured LDAP (port 389), however I'd rather not transmit a user/pass combination in clear text. But when I change to LDAP + SSL (port 636), I get the following exception:

此代码在不安全的 LDAP(端口 389)上运行良好,但是我不想以明文形式传输用户/密码组合。但是当我更改为 LDAP + SSL(端口 636)时,出现以下异常:

System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.
  at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
  at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
  at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
  at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
  at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
  at (my code)

Port 636 works for other activities, such as looking up non-password information for that LDAP/AD entry...

端口 636 适用于其他活动,例如查找该 LDAP/AD 条目的非密码信息...

UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, _username)

...so I know it's not my LDAP server's SSL setup, since it works over SSL for other lookups.

...所以我知道这不是我的 LDAP 服务器的 SSL 设置,因为它通过 SSL 进行其他查找。

Has anyone gotten the ValidateCredentials(...)call to work over SSL? Can you explain how? Or is there another/better way to securely validate AD/LDAP credentials?

有没有人接到ValidateCredentials(...)通过 SSL 工作的电话?你能解释一下怎么做吗?或者是否有另一种/更好的方法来安全地验证 AD/LDAP 凭据?

采纳答案by Nate Sauber

I was able to validate credentials using the System.DirectoryServices.Protocolsnamespace, thanks to a co-worker. Here's the code:

System.DirectoryServices.Protocols感谢一位同事,我能够使用命名空间验证凭据。这是代码:

// See http://support.microsoft.com/kb/218185 for full list of LDAP error codes
const int ldapErrorInvalidCredentials = 0x31;

const string server = "sd.example.com:636";
const string domain = "sd.example.com";

try
{
    using (var ldapConnection = new LdapConnection(server))
    {
        var networkCredential = new NetworkCredential(_username, _password, domain);
        ldapConnection.SessionOptions.SecureSocketLayer = true;
        ldapConnection.AuthType = AuthType.Negotiate;
        ldapConnection.Bind(networkCredential);
    }

    // If the bind succeeds, the credentials are valid
    return true;
}
catch (LdapException ldapException)
{
    // Invalid credentials throw an exception with a specific error code
    if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
    {
        return false;
    }

    throw;
}

I'm not thrilled with using a try/catch block to control decisioning logic, but it's what works. :/

我对使用 try/catch 块来控制决策逻辑并不感到兴奋,但它确实有效。:/

回答by mijail

Maybe this is another way. There's nothing unusual in validate credentials. The ContextOptions must set properly.

也许这是另一种方式。验证凭据没有什么异常。ContextOptions 必须正确设置。

Default value:

默认值:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing

Add Ssl:

添加SSL:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing | ContextOptions.SecureSocketLayer

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing | ContextOptions.SecureSocketLayer

ContextOptions.Negotiate or ContextOptions.SimpleBind is required. Or whatever your server need to perform authentication. ContextOptions only supports OR bit to bit.

ContextOptions.Negotiate 或 ContextOptions.SimpleBind 是必需的。或者您的服务器需要执行身份验证的任何内容。ContextOptions 仅支持 OR 位到位。

You could try also set the ContextOptions directly this way in ValidateCredentials method.

您也可以尝试在 ValidateCredentials 方法中以这种方式直接设置 ContextOptions。

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
{
    return pc.ValidateCredentials(_username, _password);
}

Or

或者

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
    return pc.ValidateCredentials(_username, _password, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
}

回答by Quarkly

For me, the ValidateCredentials method works just fine. The problem, I found, was on the server hosting the AD (I'm using AD LDS). You needed to associate the server certificate with the AD instance. So if your instance was called 'MyAD' (or ActiveDirectoryWebService), you needed to open up the MMC, snap in the 'Certificates' module, select 'Service Account' and then select 'MyAD' from the list. From there you can add the SSL certificate into the 'MyAD' Personal store. This finally kicked the SSL processing into gear.

对我来说,ValidateCredentials 方法工作得很好。我发现问题出在托管 AD 的服务器上(我使用的是 AD LDS)。您需要将服务器证书与 AD 实例相关联。因此,如果您的实例名为“MyAD”​​(或 ActiveDirectoryWebService),则您需要打开 MMC,在“证书”模块中单击,选择“服务帐户”,然后从列表中选择“MyAD”​​。从那里您可以将 SSL 证书添加到“MyAD”​​个人存储中。这最终启动了 SSL 处理。

I suspect, from what I know of the LdapConnectionmethod and the fact that you omitted the callback function, that you are not validating your server certificate. It's a messy job and ValidateCredentialsdoes it for free. Probably not a big deal, but a security hole none-the-less.

我怀疑,根据我对LdapConnection方法的了解以及您省略了回调函数的事实,您没有验证您的服务器证书。这是一项杂乱的工作,而ValidateCredentials免费提供。可能没什么大不了的,但仍然是一个安全漏洞。

回答by Ingo

I know this is old, but for anybody running into this again:

我知道这很旧,但是对于再次遇到此问题的任何人:

PrincipalContext.ValidateCredentials(...), by default, tries to open an SSL connection (ldap_init(NULL, 636)) followed by setting the option LDAP_OPT_FAST_CONCURRENT_BIND.

PrincipalContext.ValidateCredentials(...),默认情况下,尝试打开 SSL 连接 ( ldap_init(NULL, 636)),然后设置选项LDAP_OPT_FAST_CONCURRENT_BIND

If a (trusted?) client certificateis present, however, the LDAP connection is implicitly bound and fast bind cannot be enabled anymore. PrincipalContext doesn't consider this case and fails with an unexpected DirectoryOperationException.

但是,如果存在(受信任的?)客户端证书,则 LDAP 连接是隐式绑定的,并且无法再启用快速绑定。PrincipalContext 不考虑这种情况,并因意外的DirectoryOperationException.

Workaround:To support SSL where possible, but have a fallback, call ValidateCredentials(...)with default options first (i.e. no options). If this fails with the DirectoryOperationException, try again by specifying the ContextOptions(Negotiate | Sealing | Signing), which is what ValidateCredentialsinternally does for the expected LdapExceptionanyway.

解决方法:要在可能的情况下支持 SSL,但有后备,ValidateCredentials(...)请先使用默认选项(即无选项)调用。如果此操作失败,请DirectoryOperationException通过指定ContextOptions(Negotiate | Sealing | Signing)重试,无论如何,这在ValidateCredentials内部是为预期所做的LdapException