PrincipalContext.ValidateCredentials总是返回FALSE。

18

我有一个MVC应用程序需要登录并根据Active Directory验证用户。我正在使用PrincipalContext.ValidateCredentials方法,但总是返回false

连接到服务器没有问题。问题似乎出现在ValidateCredentials方法中。

这是我的代码:

public static bool IsAuthenticated(string domain, string username, string pwd) {
    bool IsAuthenticated = false;

    try {
        PrincipalContext insPrincipalContext = 
            new PrincipalContext(ContextType.Domain, domain, "DC=c1w,DC=com");

        username = "c1w\\" + username;

        IsAuthenticated = insPrincipalContext.ValidateCredentials(username, pwd);
    }
    catch (Exception ex)
    {
        // Rethrow this exception
        ExceptionPolicy.HandleException(ex, "Exception Policy");
    }

    return IsAuthenticated;
}

有人知道为什么会发生这种情况吗?

3个回答

20
这是ValidateCredentials(string, string)的工作方式:首先,它尝试使用Negotiate、Signing和Sealing上下文选项进行身份验证。如果失败,则再次尝试使用SimpleBind和SecureSocketLayer。

问题在于NT4格式(即DOMAIN\UserName或更正确地说NetBiosName\SamAccountName)与Negotiate不兼容。但它可以与SimpleBind一起使用。

因此,在调用两个参数的ValidateCredentials()方法时,可能会出现以下情况:由于不喜欢NT4格式,首先使用Negotiate失败,然后再使用简单绑定时再次失败。

在我的测试中,我发现即使回退到使用简单绑定后仍然失败的原因是它不仅使用简单绑定。它使用的是SimpleBind加上SecureSocketLayer。这意味着如果Active Directory服务器没有正确设置为使用SSL(测试环境中常见的情况),它仍将失败。

如评论中提到的,您永远不要只使用SimpleBind(不带SecureSocketLayer),否则您的密码将以明文形式通过网络发送。

在野外,我发现一些Active Directory系统根本不允许使用简单绑定,因此必须使其与Negotiate正常工作。

我发现解决此问题的2种方法:

1)如果所有操作都在同一个域上,则应该能够仅使用用户名(SAM帐户名)调用ValidateCredentials,省略“DOMAIN\”部分。然后,它将在第一次使用Negotiate时正常工作。

2)如果域部分很重要,因为可能涉及多个域(即Domain1\UserA和Domain2\UserA是不同的人),则会变得更加复杂。在这种情况下,我最终做的是将NT4名称(DOMAIN\User)转换为“用户主体名称”格式(例如LogonName@domain.com)。有几种不同的方法可以实现这一点。最简单的方法可能是使用UserPrincipal.FindByIdentity()的3个参数重载,然后获取结果的属性的值。另一种方法是使用DirectorySearcher并查询LDAP://domain中与匹配的值的用户的属性。注意:此解决方案仅在涉及的所有域都在同一个森林中时才起作用。


根据答案,Domain\UserName 在 Negotiate 中不起作用。但是当在方法调用中显式提供 ContextOptions(如 context.ValidateCredentials(userName, password, ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing))时,为什么它会起作用呢?这很令人困惑。 - managerger

9
我没有看到您初始化“pwd”变量的地方。也许您应该在此方法中使用ContextOption来指定所需的精确行为。抱歉回复过于笼统,但您的问题中没有太多细节。

已编辑问题以包括整个方法。将尝试使用ContextOption建议。谢谢。 - Billy Logan
5
B-Rain, ContextOption 参考文献指引我找到了正确的方向。最终我在调用 AD 时使用了 ContextOptions.Negotiate,而在验证凭据时使用了 ContextOptions.SimpleBind。由于网站将采用 SSL 安全协议,所以 Simple Bind 对我来说是可行的。 感谢您的帮助。 - Billy Logan
我给问题和答案都点了赞,因为这也帮助了我解决了我的问题。在我的情况下,我的开发机器(在未指定上下文的情况下可以登录)位于网络的安全区域,但Web服务器(在未指定上下文的情况下无法登录)位于DMZ中。我使用了与@Billy Logan相同的配置-在对AD的调用上进行协商,在验证调用上进行SimpleBind。 - Matt Mills
那么你用了什么?ContextOptions.Negotiate 还是 ContextOptions.SimpleBind? - Tien Do
2
如果您使用ContextOptions.Simplebind,请注意密码可能会以明文形式通过网络发送。 - ms007

1

看起来你正在使用 domain\userName 格式对用户进行验证。你可能想要从 userName 中解析出域名并使用 ValidateCredential。


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