如何使用System.DirectoryServices.Protocol验证用户名和密码?

5
首先,我不能使用Active Directory,因此无法直接使用System.DirectoryServices。这将是一台PC向仅支持System.DirectoryServices.Protocol的Novell网络发送查询。
我相当确定我现在需要提供正确的SearchRequest。
到目前为止,我已经做了这些:
private static String _certificatePath;
private static String _server;

private static SearchResponse Query(String user, String pwd, out String error)
{
    SearchResponse result = null;
    error = String.Empty;
    if (File.Exists(_certificatePath))
    {
        var identifier = new LdapDirectoryIdentifier(_server, false, false);
        try
        {
            using (var connection = new LdapConnection(identifier))
            {
                connection.SessionOptions.ProtocolVersion = 3;
                var cert = new X509Certificate();
                cert.Import(_certificatePath, null, X509KeyStorageFlags.DefaultKeySet);
                connection.ClientCertificates.Add(cert);
                connection.AuthType = AuthType.External;
                connection.AutoBind = false;
                var request = new SearchRequest()
                {
                    DistinguishedName = user, //Find this person
                    Filter = "(objectClass=*)", //The type of entry we are looking for
                    Scope = System.DirectoryServices.Protocols.SearchScope.Subtree, //We want all entries below this ou
                };
                result = (SearchResponse)connection.SendRequest(request); //Run the query and get results
            }
        } catch (Exception err)
        {
            error = String.Format("SDSP::Query {0}: {1}", err.GetType(), err.Message);
        }
    }
    else
    {
        error = "The system cannot find the Cryptography Certificate at the path specified in the Application Configuration file.";
    }
    return result;
}

我该如何创建一个搜索请求以验证用户/密码组合?
var request = new SearchRequest()
{
    DistinguishedName = user, //Find this person
    Filter = "(objectClass=*)", //The type of entry we are looking for
    Scope = System.DirectoryServices.Protocols.SearchScope.Subtree, //We want all entries below this ou
};

这里也有同样的问题。你解决了吗? - ClownCoder
@ClownCoder - 我没有,但感谢你的提醒。我现在可以为此发布赏金。 - user153923
我有完全相同的情况,已经花了几天时间尝试验证用户/密码对。在我的情况下,LDAP 配置为仅允许绑定到一个用户(管理员),因此我唯一的验证用户/密码的方法是使用此“管理员”进行绑定,然后搜索和比较某些属性或类似的东西。互联网上的每个地方都说我必须比较“userPassword”属性,但显然该属性在此 LDAP 配置中不存在。我很迷失。 - ClownCoder
1
@ClownCoder - 尝试找到安装证书的方法。如果您正在尝试访问Novell服务器,那么它将是来自Novell的东西。一旦加载了该证书(就像我在上面的示例中所做的那样),您就不需要使用“admin”帐户登录。 - user153923
1
我已经尝试过了,但它不起作用,但我怀疑我的情况与关闭的功能有关。请查看我在AsifAli72090答案下的评论... - ClownCoder
1
networkCredential对象似乎很合适,除非我弄错了。https://msdn.microsoft.com/en-us/library/bb332056.aspx - user4573148
2个回答

4

让我展示一下我最好的尝试来实现这个验证,也许它会对你有用。

在我的情况下,这并不起作用,因为我的管理员用户无法读取属性“userPassword”,我无法找出原因。我猜是因为没有分配某些权限。

无论如何,这是代码,希望能帮到你:

        var server = "<SERVER:PORT>";
        var adminUser = "<USERNAME>";
        var adminPass = "<PASSWORD>";

        using (var ldap = new LdapConnection(server))
        {
            ldap.SessionOptions.ProtocolVersion = 3;
            // To simplify this example I'm not validating certificate. Your code is fine.
            ldap.SessionOptions.VerifyServerCertificate += (connection, certificate) => true;
            ldap.SessionOptions.SecureSocketLayer = true;

            ldap.AuthType = AuthType.Basic;
            ldap.Bind(new System.Net.NetworkCredential($"cn={adminUser},o=<ORGANIZATION>", adminPass));

            // Now I will search to find user's DN.
            // If you know exact DN, then you don't need to search, go to compare request directly.
            var search = new SearchRequest
            {
                //Here goes base DN node to start searching. Node closest to entry improves performance.
                // Best base DN is one level above.
                DistinguishedName = "<BASEDN>", //i.e.: ou=users,o=google
                Filter = "uid=<USERNAME>",
                Scope = SearchScope.OneLevel
            };

            // Adding null to attributes collection, makes attributes list empty in the response.
            // This improves performance because we don't need any info of the entry.
            search.Attributes.Add(null);

            var results = (SearchResponse)ldap.SendRequest(search);

            if (results.Entries.Count == 0)
                throw new Exception("User not found");

            // Because I'm searching "uid" can't exists more than one entry.
            var entry = results.Entries[0];

            // Here I use DN from entry found.
            var compare = new CompareRequest(entry.DistinguishedName, new DirectoryAttribute("userPassword", "<PASSWORD>"));
            var response = (CompareResponse)ldap.SendRequest(compare);

            if (response.ResultCode != ResultCode.CompareTrue)
                throw new Exception("User and/or Password incorrect.");
        }

今天AsifAli72090发布了另一个答案。它听起来很合理:尝试使用提供的用户名和密码组合进行绑定。我暂时无法访问Novell系统。你能否尝试这段代码,并告诉我它是否在你那边工作? - user153923
@jp2code AsifAli72090发布的代码示例是最简单和最高效的方法。但是你需要为每个用户授予绑定权限。如果你没有任何限制(就像我一样),那么那个是最好的解决方案。如果你处于一个不能允许每个人绑定的上下文中,那么验证用户/密码的唯一方法是通过“userPassword”属性比较密码。我的第一次尝试是这个,在我的环境中它不起作用。将在其他答案中评论我所知道的线索... - ClownCoder
尝试使用CompareRequest时,对于某些帐户它似乎可以工作,但对于某些帐户我收到错误消息“所请求的属性不存在。00002080:AtrErr:DSID-03080155,#1: 0:00002080:DSID-03080155,问题1001(NO_ATTRIBUTE_OR_VAL),数据0,Att 23(userPassword)”。对于“有点工作”的帐户,即使我将密码更改为其他内容,它们仍返回true。 - Victorio Berra

2

在Windows系统上

您可以在ValidateCredentials(用户名和密码)中添加ContextOptions.Negotiate参数。

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;
}

参考资料:


关于 Novell

DirectoryEntryDirectorySearcher 都是高级别的类工具,它们是 Active Directory 的包装器。

//use the users credentials for the query
DirectoryEntry root = new DirectoryEntry(
    "LDAP://dc=domain,dc=com", 
    loginUser, 
    loginPassword
    );

//query for the username provided
DirectorySearcher searcher = new DirectorySearcher(
    root, 
    "(sAMAccountName=" + loginUser + ")"
    );    

//a success means the password was right
bool success = false; 
try {
    searcher.FindOne();
    success = true;
}
catch {
    success = false;
}

Referred to the answer.


这是我第一次尝试验证用户/密码,因为它不起作用,所以我开始使用CompareRequest。最近我发现的是,如果我在用户上启用Universal Password,它将通过“bind”以及与“admin”用户绑定的CompareRequest进行验证。因此,我的结论是,在没有打开该功能的情况下,无法验证用户/密码对。无论如何,我仍将继续尝试其他方法... - ClownCoder
1
@ClownCoder,是的,这个关于身份验证类型的问题表明AuthType.Negotiate是Windows特定的,因此在Novell上不会被接受。 - user153923
1
你在这方面提供了很多帮助,Asif。谢谢! - user153923
2
该死。DirectoryEntryDirectorySearcher都是高级类工具,它们是Active Directory的包装器。它们不在System.DirectoryServices.Protocols命名空间中。我不能使用这个答案。 - user153923
1
由于Novell没有响应Active Directory调用的机制,因此无法使用以"On Novell"开头的部分。话虽如此,回到你的第一个代码段,我在这里找到了一篇关于Apache服务器的帖子,他们说当与用户名和密码一起使用时,使用connection.AuthType = AuthType.Basic;可以使其正常工作。我尚未在我们客户的Novell系统上进行测试,但它看起来逻辑上是正确的。 - user153923

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接