如何在不将密码放入字符串的情况下检查 ActiveDirectory 上的用户/密码组合?

发布于 2024-10-30 21:13:58 字数 443 浏览 0 评论 0原文

我想检查 Windows 域上的用户/密码组合。现在我用下面的代码来做:

bool Login(String username, String password) {
    var principalContext = new PrincipalContext(ContextType.Domain);
    principalContext.ValidateCredentials(username, password);
}

虽然它可以工作,但让我烦恼的是我必须将密码放在 String 中才能使用该 API;由于我使用 SecureString 在其他地方存储密码,我真的很想使用某种方法来检查用户名/密码组合,而不必将密码作为托管 System.String 传递

实现这一目标的最佳方法是什么?

I want to check User/Password combination on a Windows domain. Right now I do it with the following code:

bool Login(String username, String password) {
    var principalContext = new PrincipalContext(ContextType.Domain);
    principalContext.ValidateCredentials(username, password);
}

While it works, the thing that bugs me is that I have to put the password in a String in order to use that API; as I am using a SecureString to store the password everywhere else, I would really like to use some way of checking the username / password combination without having to pass the password as a managed System.String.

What would be the best way of achieving that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

感情废物 2024-11-06 21:13:58

您可以尝试的一种方法可能是:

通过使用 P/Invoke 调用 LoginUser 来模拟用户,并将密码作为 SecureString 如 MSDN 中所述。

使用模拟用户连接到 ActiveDirectory,无需传递用户名和密码:

AuthenticationTypes authenticationTypes = AuthenticationTypes.Secure;

using (var entry = new DirectoryEntry("LDAP://example.com", "", "", authenticationTypes))
{
    ...
}

我还没有尝试过此操作,但在我看来它应该可以工作。

One approach you could try might be:

Impersonate the user by calling LoginUser using P/Invoke, passing the password as SecureString as described in MSDN.

Connect to ActiveDirectory with the impersonated user, without passing the username and password:

AuthenticationTypes authenticationTypes = AuthenticationTypes.Secure;

using (var entry = new DirectoryEntry("LDAP://example.com", "", "", authenticationTypes))
{
    ...
}

I haven't tried this but it seems to me it ought to work.

温柔戏命师 2024-11-06 21:13:58

使用 DsBindWithCred。请注意,即使凭据在技术上有效(例如帐户被锁定),此功能也会因访问被拒绝而失败。如果您想要该级别的详细信息,则必须使用 LogonUser 函数,但每次调用都将计为一次登录尝试。

using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Text;

public class PInvoke
{
    public static bool TestCreds(string usernamePossiblyWithDomain, 
                                 SecureString password, 
                                 string dnsDomainName)
    {
        string username, usernameDomain;
        ParseUserName(usernamePossiblyWithDomain, out username, out usernameDomain);

        IntPtr pPass = Marshal.SecureStringToGlobalAllocUnicode(password);

        try
        {
            IntPtr hDS = IntPtr.Zero;
            IntPtr authID = MakePassCreds(username, usernameDomain, pPass);
            //if you're really paranoid, you can uncomment the next two lines
            //to zero out the memory as soon as possible
            //Marshal.ZeroFreeGlobalAllocUnicode(pPass);
            //pPass = IntPtr.Zero;

            try
            {
                int lastErr = DsBindWithCred(null, dnsDomainName, authID, ref hDS);
                switch(lastErr)
                {
                    case 0: return true;  //ERROR_SUCCESS
                    case 5: return false; //ERROR_ACCESS_DENIED
                    default: throw new Win32Exception(lastErr);
                }
            }
            finally
            {
                if(hDS != IntPtr.Zero) DsUnBind(ref hDS);
                if(authID != IntPtr.Zero) DsFreePasswordCredentials(authID);
            }
        }
        finally
        {
            if(pPass != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(pPass);
        }
    }

    [DllImport("credui.dll", CharSet = CharSet.Unicode)]
    protected static extern int CredUIParseUserName(string pszUserName,
                                    StringBuilder pszUser,   int ulUserMaxChars, 
                                    StringBuilder pszDomain, int ulDomainMaxChars);

    public static void ParseUserName(string usernamePossiblyWithDomain, 
                                     out string username, out string domain)
    {
        int MaxUserChars = 256, maxDomainChars = 256;
        StringBuilder sbUser = new StringBuilder(maxUserChars);
        StringBuilder sbDomain = new StringBuilder(maxDomainChars);
        int lastErr = CredUIParseUserName(usernamePossiblyWithDomain, sbUser,
                                 maxUserChars - 1, sbDomain, maxDomainChars - 1);
        if(lastErr != 0) throw new Win32Exception(lastErr);
        username = sbUser.ToString();
        domain = sbDomain.ToString();
    }

    [DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
    protected static extern int DsMakePasswordCredentials(
        string User, string Domain, IntPtr Password, ref IntPtr pAuthIdentity);

    [DllImport("ntdsapi.dll")]
    public static extern int DsFreePasswordCredentials(IntPtr AuthIdentity);

    //caller is responsible for calling DsFreePasswordCredentials on the return val
    public static IntPtr MakePassCreds(string username, string domain, IntPtr pPass)
    {
        IntPtr auth = IntPtr.Zero;
        int lastErr = DsMakePasswordCredentials(username, domain, pPass, ref auth);
        if(lastErr != 0) throw new Win32Exception(lastErr);
        return auth;
    }

    [DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
    protected static extern int DsBindWithCred(string DomainControllerName, 
                        string DnsDomainName, IntPtr AuthIdentity, ref IntPtr phDS);

    [DllImport("ntdsapi.dll")]
    public static extern int DsUnBind(ref IntPtr phDS);
}

Use DsBindWithCred. Note that this function fails with Access Denied even when the credentials are technically valid, such as the account being locked out. You will have to use the LogonUser function if you want that level of detail, but each call will count as a login attempt.

using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Text;

public class PInvoke
{
    public static bool TestCreds(string usernamePossiblyWithDomain, 
                                 SecureString password, 
                                 string dnsDomainName)
    {
        string username, usernameDomain;
        ParseUserName(usernamePossiblyWithDomain, out username, out usernameDomain);

        IntPtr pPass = Marshal.SecureStringToGlobalAllocUnicode(password);

        try
        {
            IntPtr hDS = IntPtr.Zero;
            IntPtr authID = MakePassCreds(username, usernameDomain, pPass);
            //if you're really paranoid, you can uncomment the next two lines
            //to zero out the memory as soon as possible
            //Marshal.ZeroFreeGlobalAllocUnicode(pPass);
            //pPass = IntPtr.Zero;

            try
            {
                int lastErr = DsBindWithCred(null, dnsDomainName, authID, ref hDS);
                switch(lastErr)
                {
                    case 0: return true;  //ERROR_SUCCESS
                    case 5: return false; //ERROR_ACCESS_DENIED
                    default: throw new Win32Exception(lastErr);
                }
            }
            finally
            {
                if(hDS != IntPtr.Zero) DsUnBind(ref hDS);
                if(authID != IntPtr.Zero) DsFreePasswordCredentials(authID);
            }
        }
        finally
        {
            if(pPass != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(pPass);
        }
    }

    [DllImport("credui.dll", CharSet = CharSet.Unicode)]
    protected static extern int CredUIParseUserName(string pszUserName,
                                    StringBuilder pszUser,   int ulUserMaxChars, 
                                    StringBuilder pszDomain, int ulDomainMaxChars);

    public static void ParseUserName(string usernamePossiblyWithDomain, 
                                     out string username, out string domain)
    {
        int MaxUserChars = 256, maxDomainChars = 256;
        StringBuilder sbUser = new StringBuilder(maxUserChars);
        StringBuilder sbDomain = new StringBuilder(maxDomainChars);
        int lastErr = CredUIParseUserName(usernamePossiblyWithDomain, sbUser,
                                 maxUserChars - 1, sbDomain, maxDomainChars - 1);
        if(lastErr != 0) throw new Win32Exception(lastErr);
        username = sbUser.ToString();
        domain = sbDomain.ToString();
    }

    [DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
    protected static extern int DsMakePasswordCredentials(
        string User, string Domain, IntPtr Password, ref IntPtr pAuthIdentity);

    [DllImport("ntdsapi.dll")]
    public static extern int DsFreePasswordCredentials(IntPtr AuthIdentity);

    //caller is responsible for calling DsFreePasswordCredentials on the return val
    public static IntPtr MakePassCreds(string username, string domain, IntPtr pPass)
    {
        IntPtr auth = IntPtr.Zero;
        int lastErr = DsMakePasswordCredentials(username, domain, pPass, ref auth);
        if(lastErr != 0) throw new Win32Exception(lastErr);
        return auth;
    }

    [DllImport("ntdsapi.dll", CharSet = CharSet.Unicode)]
    protected static extern int DsBindWithCred(string DomainControllerName, 
                        string DnsDomainName, IntPtr AuthIdentity, ref IntPtr phDS);

    [DllImport("ntdsapi.dll")]
    public static extern int DsUnBind(ref IntPtr phDS);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文