ตรวจสอบชื่อผู้ใช้และรหัสผ่านกับ Active Directory หรือไม่


526

ฉันจะตรวจสอบชื่อผู้ใช้และรหัสผ่านกับ Active Directory ได้อย่างไร ฉันแค่ต้องการตรวจสอบว่าชื่อผู้ใช้และรหัสผ่านถูกต้องหรือไม่

คำตอบ:


642

หากคุณทำงานกับ. NET 3.5 หรือใหม่กว่าคุณสามารถใช้System.DirectoryServices.AccountManagementเนมสเปซและตรวจสอบข้อมูลประจำตัวของคุณได้อย่างง่ายดาย:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

มันง่ายและน่าเชื่อถือมันเป็นรหัสที่จัดการ 100% C # ในตอนท้ายของคุณคุณจะขออะไรอีก :-)

อ่านทั้งหมดได้ที่นี่:

ปรับปรุง:

ตามที่ระบุไว้ในคำถาม SO อื่น ๆ (และคำตอบ)มีปัญหากับการโทรนี้ที่อาจส่งคืนTrueรหัสผ่านเก่าของผู้ใช้ เพิ่งทราบถึงพฤติกรรมนี้และอย่าแปลกใจถ้าเกิดเหตุการณ์นี้ :-) (ขอบคุณ @MikeGledhill สำหรับการชี้เรื่องนี้!)


36
ในโดเมนของฉันฉันต้องระบุ pc.ValidateCredentials ("myuser", "mypassword", ContextOptions.Negotiate) หรือฉันจะได้รับ System.DirectoryServices.Protocols.DirectoryOperationException: เซิร์ฟเวอร์ไม่สามารถจัดการคำขอไดเรกทอรี
Alex Peck

12
หากรหัสผ่านหมดอายุหรือบัญชีถูกปิดใช้งานแล้ว ValidateCredentials จะคืนค่าเท็จ โชคไม่ดีที่มันไม่ได้บอกคุณว่าทำไมมันถึงกลับเป็นเท็จ (ซึ่งน่าเสียดายเพราะมันหมายความว่าฉันไม่สามารถทำสิ่งที่มีเหตุผลเช่นเปลี่ยนเส้นทางผู้ใช้เพื่อเปลี่ยนรหัสผ่าน)
Chris J

64
นอกจากนี้ระวังบัญชี 'ผู้มาเยือน' - ถ้าระดับโดเมนบัญชี Guest ถูกเปิดใช้งาน ValidateCredentials ผลตอบแทนจริงถ้าคุณจะให้มันไม่ existantผู้ใช้ ดังนั้นคุณอาจต้องการโทรUserPrinciple.FindByIdentityเพื่อดูว่า ID ผู้ใช้ที่ส่งผ่านนั้นมีอยู่ก่อนหรือไม่
Chris J

7
@AlexPeck: เหตุผลที่คุณต้องทำเช่นนี้ (เช่นฉัน) คือ. NET ใช้เทคโนโลยีต่อไปนี้ตามค่าเริ่มต้น: LDAP + SSL, Kerberos และ RPC ฉัน RPC ผู้ต้องสงสัยเป็นปิดในเครือข่ายของคุณ (ดี) และ Kerberos ไม่จริงรับใช้โดย .NET ContextOptions.Negotiateจนกว่าคุณจะบอกอย่างชัดเจนโดยใช้
Brett Veenstra

5
โปรดทราบว่าหากผู้ใช้เปลี่ยนรหัสผ่าน Active Directory รหัสชิ้นนี้จะยังคงรับรองความถูกต้องของผู้ใช้โดยใช้รหัสผ่าน AD เก่าของพวกเขาอย่างมีความสุข ใช่จริง ๆ อ่านได้ที่นี่: stackoverflow.com/questions/8949501/…
Mike Gledhill

70

เราทำสิ่งนี้กับอินทราเน็ตของเรา

คุณต้องใช้ System.DirectoryServices

นี่คือความกล้าของรหัส

using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
    using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
    {
        //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
        adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";

        try
        {
            SearchResult adsSearchResult = adsSearcher.FindOne();
            bSucceeded = true;

            strAuthenticatedBy = "Active Directory";
            strError = "User has been authenticated by Active Directory.";
        }
        catch (Exception ex)
        {
            // Failed to authenticate. Most likely it is caused by unknown user
            // id or bad strPassword.
            strError = ex.Message;
        }
        finally
        {
            adsEntry.Close();
        }
    }
}

9
คุณใส่อะไรใน "เส้นทาง"? ชื่อโดเมน? ชื่อของเซิร์ฟเวอร์ เส้นทาง LDAP ไปยังโดเมนหรือไม่ เส้นทาง LDAP ไปยังเซิร์ฟเวอร์หรือไม่
Ian Boyd

3
คำตอบ 1: ไม่เราเรียกมันว่าเป็นบริการบนเว็บเพื่อให้สามารถเรียกได้จากหลาย ๆ ที่ในเว็บแอพหลัก คำตอบ 2: เส้นทางประกอบด้วยข้อมูล LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com
DiningPhilanderer

3
ดูเหมือนว่าสิ่งนี้อาจอนุญาตการฉีด LDAP คุณอาจต้องการให้แน่ใจว่าจะหลบหนีหรือลบวงเล็บใด ๆ ใน strAccountId
Brain2000

นี่หมายความว่าstrPasswordเก็บไว้ใน LDAP เป็นข้อความธรรมดาหรือไม่?
Matt Kocaj

15
มีไม่ควรจะจำเป็นต้องโทรอย่างชัดเจนClose()บนusingตัวแปร
Nyerguds

62

โซลูชันหลายตัวที่นำเสนอที่นี่ขาดความสามารถในการแยกแยะความแตกต่างระหว่างผู้ใช้ / รหัสผ่านผิดและรหัสผ่านที่จำเป็นต้องเปลี่ยน สามารถทำได้ด้วยวิธีต่อไปนี้:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

namespace ProtocolTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
                NetworkCredential credential = new NetworkCredential("user", "password");
                connection.Credential = credential;
                connection.Bind();
                Console.WriteLine("logged in");
            }
            catch (LdapException lexc)
            {
                String error = lexc.ServerErrorMessage;
                Console.WriteLine(lexc);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }
    }
}

หากรหัสผ่านผู้ใช้ไม่ถูกต้องหรือไม่มีผู้ใช้ข้อผิดพลาดจะมี

"8009030C: LdapErr: DSID-0C0904DC, ความคิดเห็น: ข้อผิดพลาด AcceptSecurityContext, ข้อมูล 52e, v1db1",

หากจำเป็นต้องเปลี่ยนรหัสผ่านผู้ใช้รหัสผ่านจะมี

"8009030C: LdapErr: DSID-0C0904DC, ความคิดเห็น: ข้อผิดพลาด AcceptSecurityContext, ข้อมูล 773, v1db1"

lexc.ServerErrorMessageค่าข้อมูลเป็นตัวแทนฐานสิบหกของข้อผิดพลาด Win32 รหัส เหล่านี้เป็นรหัสข้อผิดพลาดเดียวกันซึ่งจะถูกส่งกลับโดยมิฉะนั้นเรียกการเรียก Win32 LogonUser API รายการด้านล่างสรุปช่วงของค่าทั่วไปที่มีค่าฐานสิบหกและทศนิยม:

525 user not found ​(1317)
52e invalid credentials ​(1326)
530 not permitted to logon at this time (1328)
531 not permitted to logon at this workstation (1329)
532 password expired ​(1330)
533 account disabled ​(1331) 
701 account expired ​(1793)
773 user must reset password (1907)
775 user account locked (1909)

2
น่าเสียดายที่การติดตั้ง AD บางอันไม่ส่งคืนรหัสข้อผิดพลาดย่อย LDAP ซึ่งหมายความว่าโซลูชันนี้จะไม่ทำงาน
Søren Mors

4
อย่าลืมเพิ่มการอ้างอิงถึงโครงการ: System.DirectoryServicesและSystem.DirectoryServices.Protocols
TomXP411

3
อย่างไรก็ตามคำถามของฉันคือ: คุณจะรับชื่อเซิร์ฟเวอร์ LDAP ได้อย่างไร หากคุณกำลังเขียนแอปพลิเคชันแบบพกพาคุณไม่สามารถคาดหวังได้ว่าผู้ใช้จะรู้หรือจำเป็นต้องระบุชื่อเซิร์ฟเวอร์โฆษณาในทุกเครือข่าย
TomXP411

1
ฉันมีผู้ใช้ที่ถูก จำกัด ให้เข้าสู่ระบบเฉพาะเวิร์กสเตชัน ฉันจะระบุเวิร์กสเตชันที่ฉันพยายามเข้าสู่ระบบได้อย่างไร (workstation1 จะล้มเหลวกับข้อมูล 531, workstation2 จะทำงานได้ดีเช่น)
akohlsmith

1
ฉันรู้สึกแปลก ๆ เพราะฉันไม่คิดว่าคุณจะได้รับเครดิตเพียงพอ นี่เป็นวิธีการจัดการอย่างเต็มรูปแบบโดยไม่มีปัญหาของการเรียก Win32 API เพื่อตรวจสอบว่า "ผู้ใช้ต้องรีเซ็ตรหัสผ่าน" ซึ่งไม่มีคำตอบอื่น ๆ ที่ทำได้อย่างชัดเจน มีช่องโหว่ใดในวิธีนี้ที่ทำให้อัตราการแข็งค่าต่ำหรือไม่ อืม ...
Lionet Chen

34

วิธีง่ายๆในการใช้ DirectoryServices:

using System.DirectoryServices;

//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
    bool authenticated = false;

    try
    {
        DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
        object nativeObject = entry.NativeObject;
        authenticated = true;
    }
    catch (DirectoryServicesCOMException cex)
    {
        //not authenticated; reason why is in cex
    }
    catch (Exception ex)
    {
        //not authenticated due to some other exception [this is optional]
    }

    return authenticated;
}

จำเป็นต้องมีการเข้าถึง NativeObject เพื่อตรวจหาผู้ใช้ / รหัสผ่านที่ไม่ดี


4
รหัสนี้ไม่ถูกต้องเนื่องจากกำลังทำการตรวจสอบการอนุญาต (ตรวจสอบว่าผู้ใช้ได้รับอนุญาตให้อ่านข้อมูลไดเรกทอรีที่ใช้งานอยู่) ชื่อผู้ใช้และรหัสผ่านนั้นถูกต้อง แต่ผู้ใช้ไม่ได้รับอนุญาตให้อ่านข้อมูล - และได้รับการยกเว้น กล่าวอีกนัยหนึ่งคุณสามารถมีชื่อผู้ใช้และรหัสผ่านที่ถูกต้อง แต่ยังคงได้รับข้อยกเว้น
เอียนบอยด์

2
ฉันจริงในกระบวนการของการขอที่พื้นเมืองเทียบเท่าPrincipleContext- ซึ่งมีอยู่เพียงใน NET 3.5 แต่ถ้าคุณใช้. NET 3.5 หรือใหม่กว่าคุณควรใช้PrincipleContext
Ian Boyd

28

น่าเสียดายที่ไม่มีวิธี "ง่าย" ในการตรวจสอบข้อมูลรับรองผู้ใช้ในโฆษณา

ด้วยวิธีการทุกวิธีที่นำเสนอคุณอาจได้รับ false-positive: เครดิตของผู้ใช้จะถูกต้อง แต่โฆษณาจะกลับเท็จภายใต้สถานการณ์บางอย่าง:

  • ผู้ใช้จะต้องเปลี่ยนรหัสผ่านที่เข้าสู่ระบบถัดไป
  • รหัสผ่านของผู้ใช้หมดอายุ

ActiveDirectory จะไม่อนุญาตให้คุณใช้ LDAP เพื่อตรวจสอบว่ารหัสผ่านไม่ถูกต้องเนื่องจากผู้ใช้ต้องเปลี่ยนรหัสผ่านหรือรหัสผ่านหมดอายุ

ในการตรวจสอบการเปลี่ยนรหัสผ่านหรือรหัสผ่านหมดอายุคุณสามารถเรียก Win32: LogonUser () และตรวจสอบรหัสข้อผิดพลาด windows สำหรับค่าคงที่ 2 ต่อไปนี้:

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

1
ผมอาจจะถามว่าคุณมี devinitions สำหรับที่หมดอายุและ Must_Change ... พบว่าพวกเขาไม่มีที่ไหนเลย แต่ที่นี่ :)
mabstrei


ขอบคุณ ฉันพยายามค้นหาวิธีการตรวจสอบของฉันคืนเท็จตลอดเวลา เป็นเพราะผู้ใช้จำเป็นต้องเปลี่ยนรหัสผ่านของเขา
Deise Vicentin

22

วิธีที่ง่ายที่สุดน่าจะเป็น PInvoke LogonUser Win32 API.eg

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

การอ้างอิง MSDN ที่นี่ ...

http://msdn.microsoft.com/en-us/library/aa378184.aspx

ต้องการใช้ประเภทการเข้าสู่ระบบแน่นอน

LOGON32_LOGON_NETWORK (3)

สิ่งนี้สร้างโทเค็นที่มีน้ำหนักเบาเท่านั้น - เหมาะสำหรับการตรวจสอบ AuthN (สามารถใช้ประเภทอื่นเพื่อสร้างเซสชันแบบโต้ตอบเป็นต้น)


@Alan ชี้ให้เห็นว่า LogonUser API มีคุณสมบัติที่มีประโยชน์มากมายนอกเหนือจากการเรียก System.DirectoryServices
stephbu

3
@cciotti: ไม่ผิด วิธีที่ดีที่สุดในการรับรองความถูกต้องของใครบางคนคือการใช้ LogonUserAPI เป็น @stephbu write วิธีการอื่น ๆ ทั้งหมดที่อธิบายในโพสต์นี้จะไม่ทำงาน 100% เพียงแค่ทราบว่าฉันเชื่อว่าคุณจะต้องเข้าร่วมโดเมนเพื่อโทรหา LogonUser
Alan

@Alan เพื่อสร้างข้อมูลรับรองคุณต้องสามารถเชื่อมต่อกับโดเมนได้โดยส่งบัญชีโดเมนที่ถูกต้อง อย่างไรก็ตามฉันค่อนข้างมั่นใจว่าเครื่องของคุณไม่จำเป็นต้องเป็นสมาชิกของโดเมน
stephbu

2
LogonUserAPI ต้องการให้ผู้ใช้มีพระราชบัญญัติเป็นส่วนหนึ่งของระบบปฏิบัติการ privelage; ซึ่งไม่ใช่สิ่งที่ผู้ใช้ได้รับและไม่ใช่สิ่งที่คุณต้องการให้กับผู้ใช้ทุกคนในองค์กร ( msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Ian Boyd

1
LogonUser ต้องการเพียงทำหน้าที่เป็นส่วนหนึ่งของระบบปฏิบัติการสำหรับ Windows 2000 และด้านล่างตามsupport.microsoft.com/kb/180548 ... ดูเหมือนว่าจะสะอาดสำหรับ Server 2003 และสูงกว่า
Chris J

18

โซลูชัน. Net แบบเต็มคือการใช้คลาสจาก System.DirectoryServices เนมสเปซ พวกเขาอนุญาตให้สอบถามเซิร์ฟเวอร์โฆษณาโดยตรง นี่คือตัวอย่างเล็ก ๆ ที่จะทำสิ่งนี้:

using (DirectoryEntry entry = new DirectoryEntry())
{
    entry.Username = "here goes the username you want to validate";
    entry.Password = "here goes the password";

    DirectorySearcher searcher = new DirectorySearcher(entry);

    searcher.Filter = "(objectclass=user)";

    try
    {
        searcher.FindOne();
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570)
        {
            // Login or password is incorrect
        }
    }
}

// FindOne() didn't throw, the credentials are correct

รหัสนี้เชื่อมต่อโดยตรงกับเซิร์ฟเวอร์โฆษณาโดยใช้ข้อมูลประจำตัวที่ให้ไว้ หากข้อมูลรับรองไม่ถูกต้อง searcher.FindOne () จะส่งข้อยกเว้น ErrorCode เป็นรหัสที่สอดคล้องกับข้อผิดพลาด COM "ชื่อผู้ใช้ / รหัสผ่านไม่ถูกต้อง"

คุณไม่จำเป็นต้องเรียกใช้รหัสในฐานะผู้ใช้ AD ในความเป็นจริงฉันใช้มันเพื่อสอบถามข้อมูลบนเซิร์ฟเวอร์โฆษณาจากไคลเอนต์นอกโดเมน!


ประเภทการรับรองความถูกต้องเป็นอย่างไร ฉันคิดว่าคุณลืมมันในรหัสของคุณด้านบน :-) โดยค่าเริ่มต้น DirectoryEntry.AuthenticationType ถูกตั้งค่าเป็นปลอดภัยใช่ไหม รหัสนั้นจะไม่ทำงานบน LDAP ที่ไม่ปลอดภัย (ไม่ระบุชื่อหรืออาจไม่มี) ฉันถูกต้องกับสิ่งนี้หรือไม่
jerbersoft

ข้อเสียของการสืบค้นเซิร์ฟเวอร์โฆษณาคือคุณมีสิทธิ์ในการสอบถามเซิร์ฟเวอร์โฆษณา ข้อมูลรับรองของคุณสามารถใช้ได้ แต่ถ้าคุณไม่ได้รับอนุญาตให้สอบถามโฆษณาคุณจะได้รับข้อผิดพลาด นั่นคือเหตุผลที่Fast Bindถูกสร้างขึ้น คุณตรวจสอบข้อมูลรับรองโดยไม่อนุญาตให้ผู้ใช้สามารถทำอะไรบางอย่างได้
Ian Boyd

2
สิ่งนี้จะไม่อนุญาตให้ทุกคนผ่านไปในกรณีที่มีการโยน COMException ด้วยเหตุผลอื่นใดก่อนที่จะมีการตรวจสอบข้อมูลรับรองหรือไม่
Stefan Paul Noack

11

อีกการเรียก. NET อีกครั้งเพื่อรับรองความถูกต้องของข้อมูลรับรอง LDAP อย่างรวดเร็ว:

using System.DirectoryServices;

using(var DE = new DirectoryEntry(path, username, password)
{
    try
    {
        DE.RefreshCache(); // This will force credentials validation
    }
    catch (COMException ex)
    {
        // Validation failed - handle how you want
    }
}

นี่เป็นทางออกเดียวที่ได้ผลสำหรับฉันการใช้ PrincipalContext ไม่ได้ผลสำหรับฉัน
Daniel

PrincipalContext ไม่ถูกต้องสำหรับการเชื่อมต่อ LDAP ที่ปลอดภัย (หรือที่รู้จักว่า LDAPS ซึ่งใช้พอร์ต 636
Kiquenet

10

ลองใช้รหัสนี้ (หมายเหตุ: รายงานว่าไม่ทำงานบน windows server 2000)

#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername, 
    String lpszDomain, String lpszPassword, int dwLogonType, 
    int dwLogonProvider, out int phToken);

[DllImport("Kernel32.dll")]
private static extern int GetLastError();

public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
   int token1, ret;
   int attmpts = 0;

   bool LoggedOn = false;

   while (!LoggedOn && attmpts < 2)
   {
      LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
      if (LoggedOn) return (true);
      else
      {
         switch (ret = GetLastError())
         {
            case (126): ; 
               if (attmpts++ > 2)
                  throw new LogonException(
                      "Specified module could not be found. error code: " + 
                      ret.ToString());
               break;

            case (1314): 
               throw new LogonException(
                  "Specified module could not be found. error code: " + 
                      ret.ToString());

            case (1326): 
               // edited out based on comment
               //  throw new LogonException(
               //   "Unknown user name or bad password.");
            return false;

            default: 
               throw new LogonException(
                  "Unexpected Logon Failure. Contact Administrator");
              }
          }
       }
   return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser

ยกเว้นคุณจะต้องสร้างข้อยกเว้นที่กำหนดเองสำหรับ "LogonException"


อย่าใช้การจัดการข้อยกเว้นสำหรับการส่งคืนข้อมูลจากวิธีการ "ชื่อผู้ใช้ที่ไม่รู้จักหรือรหัสผ่านไม่ถูกต้อง" นั้นไม่ธรรมดามันเป็นลักษณะการทำงานมาตรฐานสำหรับ LogonUser เพียงแค่กลับเท็จ
Treb

ใช่ ... นี่เป็นพอร์ตจากห้องสมุด VB6 เก่า ... เขียนปี 2003 หรือมากกว่านั้น ... (เมื่อ. Net ออกมาเป็นครั้งแรก)
Charles Bretana

หากใช้งานบน Windows 2000 รหัสนี้จะไม่ทำงาน ( support.microsoft.com/kb/180548 )
Ian Boyd

1
คิดใหม่นี้ พฤติกรรมที่คาดหวังของผู้ใช้เข้าสู่ระบบของจุดประสงค์ของมันคือการเข้าสู่ระบบของผู้ใช้บน ถ้ามันล้มเหลวในการทำงานนั้นมันเป็นข้อยกเว้น ที่จริงแล้วเมธอดควรคืนค่าเป็นโมฆะไม่ใช่บูลีน นอกจากนี้หากคุณเพิ่งส่งคืนบูลีนผู้บริโภคของเมธอดจะไม่มีวิธีแจ้งผู้ใช้ว่าสาเหตุของความล้มเหลวนั้นคืออะไร
Charles Bretana

5

หากคุณติดอยู่กับ. NET 2.0 และรหัสที่ได้รับการจัดการนี่เป็นอีกวิธีหนึ่งที่ใช้งานได้ทั้งบัญชีโลคอลและโดเมน:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;

static public bool Validate(string domain, string username, string password)
{
    try
    {
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo()
        {
            FileName = "no_matter.xyz",
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            LoadUserProfile = true,
            Domain = String.IsNullOrEmpty(domain) ? "" : domain,
            UserName = username,
            Password = Credentials.ToSecureString(password)
        };
        proc.Start();
        proc.WaitForExit();
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        switch (ex.NativeErrorCode)
        {
            case 1326: return false;
            case 2: return true;
            default: throw ex;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return false;
}   

ทำงานได้ดีกับบัญชีท้องถิ่นของเครื่องที่เขาเปิดตัวสคริปต์
eka808

BTW ต้องใช้วิธีนี้เพื่อทำให้งานนี้เป็นแบบสาธารณะคงที่ SecureString ToSecureString (สตริง PwString) {ถ่าน [] PasswordChars = PwString.ToCharArray (); รหัสผ่าน SecureString = ใหม่ SecureString (); foreach (char c ใน PasswordChars) Password.AppendChar (c); ProcessStartInfo foo = ใหม่ ProcessStartInfo (); foo.Password = รหัสผ่าน; กลับ foo รหัสผ่าน; }
eka808

ในทางตรงกันข้ามเราควรใช้ SecureString เป็นรหัสผ่านอยู่ดี WPF PasswordBox รองรับ
Stephen Drew

5

การรับรองความถูกต้องของ Windows อาจล้มเหลวได้ด้วยสาเหตุหลายประการ: ชื่อผู้ใช้หรือรหัสผ่านไม่ถูกต้องบัญชีที่ถูกล็อครหัสผ่านที่หมดอายุและอื่น ๆ หากต้องการแยกแยะข้อผิดพลาดเหล่านี้ให้เรียกฟังก์ชันLogonUser API ผ่าน P / Invoke และตรวจสอบรหัสข้อผิดพลาดหากฟังก์ชันส่งคืนfalse:

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

using Microsoft.Win32.SafeHandles;

public static class Win32Authentication
{
    private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() // called by P/Invoke
            : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
    }

    private enum LogonType : uint
    {
        Network = 3, // LOGON32_LOGON_NETWORK
    }

    private enum LogonProvider : uint
    {
        WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(
        string userName, string domain, string password,
        LogonType logonType, LogonProvider logonProvider,
        out SafeTokenHandle token);

    public static void AuthenticateUser(string userName, string password)
    {
        string domain = null;
        string[] parts = userName.Split('\\');
        if (parts.Length == 2)
        {
            domain = parts[0];
            userName = parts[1];
        }

        SafeTokenHandle token;
        if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
            token.Dispose();
        else
            throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
    }
}

ตัวอย่างการใช้งาน:

try
{
    Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
    // Or: Win32Authentication.AuthenticateUser("user@example.com", "P@ssw0rd");
}
catch (Win32Exception ex)
{
    switch (ex.NativeErrorCode)
    {
        case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
            // ...
        case 1327: // ERROR_ACCOUNT_RESTRICTION
            // ...
        case 1330: // ERROR_PASSWORD_EXPIRED
            // ...
        case 1331: // ERROR_ACCOUNT_DISABLED
            // ...
        case 1907: // ERROR_PASSWORD_MUST_CHANGE
            // ...
        case 1909: // ERROR_ACCOUNT_LOCKED_OUT
            // ...
        default: // Other
            break;
    }
}

หมายเหตุ: LogonUser ต้องการความสัมพันธ์แบบเชื่อถือได้กับโดเมนที่คุณกำลังตรวจสอบ


คุณช่วยอธิบายได้ไหมว่าทำไมคำตอบของคุณดีกว่าคำตอบที่ได้รับคะแนนสูงสุด
Mohammad Ali

1
@MohammadAli: หากคุณจำเป็นต้องรู้ว่าทำไมการตรวจสอบความถูกต้องของข้อมูลรับรองล้มเหลว (ข้อมูลประจำตัวที่ไม่ถูกต้อง, บัญชีที่ถูกล็อค, รหัสผ่านที่หมดอายุ, ฯลฯ ) ฟังก์ชั่น LogonUser API จะบอกคุณ ในทางตรงกันข้ามเมธอด PrincipalContext.ValidateCredentials (ตามความเห็นเกี่ยวกับคำตอบของ marc_s) จะไม่; มันกลับเท็จในทุกกรณีเหล่านี้ ในทางกลับกัน LogonUser ต้องการความสัมพันธ์แบบเชื่อถือได้กับโดเมน แต่ PrincipalContext.ValidateCredentials (ฉันคิดว่า) ไม่
Michael Liu

2

ฟังก์ชั่นที่เรียบง่ายของฉัน

 private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
    {
        try
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
            DirectorySearcher ds = new DirectorySearcher(de);
            ds.FindOne();
            return true;
        }
        catch //(Exception ex)
        {
            return false;
        }
    }

1

นี่คือโซลูชันการตรวจสอบสิทธิ์ที่สมบูรณ์แบบสำหรับการอ้างอิงของคุณ

ก่อนอื่นให้เพิ่มการอ้างอิงสี่รายการต่อไปนี้

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.