2012-04-20 12 views
7

Ich erstelle eine Xaml/C# -Anwendung, und ich möchte es mit einer Login-Eingabeaufforderung Popup.Windows Sicherheit Benutzerdefinierte Login-Validierung

Ich würde gerne wissen, ob es möglich ist, CredUIPromptForWindowsCredentials zu verwenden.

  • anzeigen Windows-Sicherheitsdialog
  • der eingegebene Benutzername & Passwort Get
  • Perform Validierung Benutzerdefinierte
  • Wenn die Validierung succes -> weiter App
  • sonst, wenn die Validierung fehlgeschlagen -> -Informiere Benutzer ungültiger Benutzername oder Passwort

Ich habe bereits Windows Security login form? undbetrachtet, aber sie erklären nicht, wie die Validierung zu handhaben ist.

Ich würde wirklich gerne ein kleines Beispiel, wo, wenn der Benutzer username = "Bo" und password = "123" dann sucsecse sonst Fehlermeldung eingeben und dem Benutzer erlauben, es erneut zu versuchen.

Die App wird auf mehreren Computern installiert.

Oder ist das einfach nicht möglich?

aktualisieren

durch die Antwort auf diese Frage Inspired Show Authentication dialog in C# for windows Vista/7

Ich habe den Code geändert wie erwartet zu funktionieren.

Bitte nicht, dass der Validierungsteil nur für den Proof of Concept gedacht ist.

WindowsSecurityDialog.cs

public class WindowsSecurityDialog 
    { 

     public string CaptionText { get; set; } 
     public string MessageText { get; set; } 

     [DllImport("ole32.dll")] 
     public static extern void CoTaskMemFree(IntPtr ptr); 

     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 
     private struct CREDUI_INFO 
     { 
      public int cbSize; 
      public IntPtr hwndParent; 
      public string pszMessageText; 
      public string pszCaptionText; 
      public IntPtr hbmBanner; 
     } 


     [DllImport("credui.dll", CharSet = CharSet.Auto)] 
     private static extern bool CredUnPackAuthenticationBuffer(int dwFlags, 
                    IntPtr pAuthBuffer, 
                    uint cbAuthBuffer, 
                    StringBuilder pszUserName, 
                    ref int pcchMaxUserName, 
                    StringBuilder pszDomainName, 
                    ref int pcchMaxDomainame, 
                    StringBuilder pszPassword, 
                    ref int pcchMaxPassword); 

     [DllImport("credui.dll", CharSet = CharSet.Auto)] 
     private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere, 
                    int authError, 
                    ref uint authPackage, 
                    IntPtr InAuthBuffer, 
                    uint InAuthBufferSize, 
                    out IntPtr refOutAuthBuffer, 
                    out uint refOutAuthBufferSize, 
                    ref bool fSave, 
                    int flags); 



     public bool ValidateUser() 
     { 
      var credui = new CREDUI_INFO 
            { 
             pszCaptionText = CaptionText, 
             pszMessageText = MessageText 
            }; 
      credui.cbSize = Marshal.SizeOf(credui); 
      uint authPackage = 0; 
      IntPtr outCredBuffer; 
      uint outCredSize; 
      bool save = false; 


      const int loginErrorCode = 1326; //Login Failed 
      var authError = 0; 

      while (true) 
      { 




       var result = CredUIPromptForWindowsCredentials(ref credui, 
                   authError, 
                   ref authPackage, 
                   IntPtr.Zero, 
                   0, 
                   out outCredBuffer, 
                   out outCredSize, 
                   ref save, 
                   1 /* Generic */); 

       var usernameBuf = new StringBuilder(100); 
       var passwordBuf = new StringBuilder(100); 
       var domainBuf = new StringBuilder(100); 

       var maxUserName = 100; 
       var maxDomain = 100; 
       var maxPassword = 100; 
       if (result == 0) 
       { 
        if (CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName, 
                 domainBuf, ref maxDomain, passwordBuf, ref maxPassword)) 
        { 
         //TODO: ms documentation says we should call this but i can't get it to work 
         //SecureZeroMem(outCredBuffer, outCredSize); 

         //clear the memory allocated by CredUIPromptForWindowsCredentials 
         CoTaskMemFree(outCredBuffer); 
         var networkCredential = new NetworkCredential() 
               { 
                UserName = usernameBuf.ToString(), 
                Password = passwordBuf.ToString(), 
                Domain = domainBuf.ToString() 
               }; 

         //Dummy Code replace with true User Validation 
         if (networkCredential.UserName == "Bo" && networkCredential.Password == "1234") 
          return true; 
         else //login failed show dialog again with login error 
         { 
          authError = loginErrorCode; 
         } 



        } 
       } 
       else return false; 


      } 
     } 
    } 

App.xaml.cs

protected override void OnStartup(StartupEventArgs e) 
     { 
      var windowsSecurityDialog = new WindowsSecurityDialog 
              { 
               CaptionText = "Enter your credentials", 
               MessageText = "These credentials will be used to connect to YOUR APP NAME"; 
              }; 

      if (windowsSecurityDialog.ValidateUser()) 
       base.OnStartup(e); 
     } 
+0

Ich denke, es ist besser, eine benutzerdefinierte Form dafür zu machen. Einfach zu verwalten und weniger kompliziert. –

+1

Ich habe eigentlich schon ein benutzerdefiniertes Formular, möchte nur sehen, ob es möglich ist, Windows zu verwenden. Auch meiner ist nicht der beste Blick :-) – gulbaek

+0

Und von "nicht das beste Aussehen", meinen Sie, es ähnelt nicht dem Windows-Sicherheitsdialog genug, um den Benutzer zu täuschen. – SPE

Antwort

4

Sie finden eine vollständige Implementierung für WPF und WinForms mit CredUIPromptForWindowsCredentials um Ookii dialogs.

3

war ich ein wenig entsetzt, als ich dachte begann dies möglich sein könnte.

Die Antwort lautet ja und nein. Sie können die Netzwerkdomäne und den Benutzernamen erreichen, aber (Gott sei Dank), Sie können nicht das tatsächliche Passwort, nur ein Hash des Passworts bekommen.

Leihen schwer von PInvoke, hier ist ein Beispiel WPF App, die den Benutzernamen und das Passwort ein und ausgibt.

-Code

using System; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Windows; 
using System.Windows.Interop; 

namespace LoginDialog 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 

      // Declare/initialize variables. 
      bool save = false; 
      int errorcode = 0; 
      uint dialogReturn; 
      uint authPackage = 0; 
      IntPtr outCredBuffer; 
      uint outCredSize; 

      // Create the CREDUI_INFO struct. 
      CREDUI_INFO credui = new CREDUI_INFO(); 
      credui.cbSize = Marshal.SizeOf(credui); 
      credui.pszCaptionText = "Connect to your application"; 
      credui.pszMessageText = "Enter your credentials!"; 
      credui.hwndParent = new WindowInteropHelper(this).Handle; 

      // Show the dialog. 
      dialogReturn = CredUIPromptForWindowsCredentials(
       ref credui, 
       errorcode, 
       ref authPackage, 
       (IntPtr)0, // You can force that a specific username is shown in the dialog. Create it with 'CredPackAuthenticationBuffer()'. Then, the buffer goes here... 
       0,   // ...and the size goes here. You also have to add CREDUIWIN_IN_CRED_ONLY to the flags (last argument). 
       out outCredBuffer, 
       out outCredSize, 
       ref save, 
       0); // Use the PromptForWindowsCredentialsFlags Enum here. You can use multiple flags if you seperate them with | . 

      if (dialogReturn == 1223) // Result of 1223 means the user canceled. Not sure if other errors are ever returned. 
       textBox1.Text += ("User cancelled!"); 
      if (dialogReturn != 0) // Result of something other than 0 means...something, I'm sure. Either way, failed or canceled. 
       return; 

      var domain = new StringBuilder(100); 
      var username = new StringBuilder(100); 
      var password = new StringBuilder(100); 
      int maxLength = 100; // Note that you can have different max lengths for each variable if you want. 

      // Unpack the info from the buffer. 
      CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, username, ref maxLength, domain, ref maxLength, password, ref maxLength); 

      // Clear the memory allocated by CredUIPromptForWindowsCredentials. 
      CoTaskMemFree(outCredBuffer); 

      // Output info, escaping whitespace characters for the password. 
      textBox1.Text += String.Format("Domain: {0}\n", domain); 
      textBox1.Text += String.Format("Username: {0}\n", username); 
      textBox1.Text += String.Format("Password (hashed): {0}\n", EscapeString(password.ToString())); 
     } 

     public static string EscapeString(string s) 
     { 
      // Formatted like this only for you, SO. 
      return s 
       .Replace("\a", "\\a") 
       .Replace("\b", "\\b") 
       .Replace("\f", "\\f") 
       .Replace("\n", "\\n") 
       .Replace("\r", "\\r") 
       .Replace("\t", "\\t") 
       .Replace("\v", "\\v"); 
     } 

     #region DLLImports 
     [DllImport("ole32.dll")] 
     public static extern void CoTaskMemFree(IntPtr ptr); 

     [DllImport("credui.dll", CharSet = CharSet.Unicode)] 
     private static extern uint CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere, int authError, ref uint authPackage, IntPtr InAuthBuffer, 
      uint InAuthBufferSize, out IntPtr refOutAuthBuffer, out uint refOutAuthBufferSize, ref bool fSave, PromptForWindowsCredentialsFlags flags); 

     [DllImport("credui.dll", CharSet = CharSet.Unicode)] 
     private static extern bool CredUnPackAuthenticationBuffer(int dwFlags, IntPtr pAuthBuffer, uint cbAuthBuffer, StringBuilder pszUserName, ref int pcchMaxUserName, StringBuilder pszDomainName, ref int pcchMaxDomainame, StringBuilder pszPassword, ref int pcchMaxPassword); 
     #endregion 

     #region Structs and Enums 
     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
     private struct CREDUI_INFO 
     { 
      public int cbSize; 
      public IntPtr hwndParent; 
      public string pszMessageText; 
      public string pszCaptionText; 
      public IntPtr hbmBanner; 
     } 

     private enum PromptForWindowsCredentialsFlags 
     { 
      /// <summary> 
      /// The caller is requesting that the credential provider return the user name and password in plain text. 
      /// This value cannot be combined with SECURE_PROMPT. 
      /// </summary> 
      CREDUIWIN_GENERIC = 0x1, 
      /// <summary> 
      /// The Save check box is displayed in the dialog box. 
      /// </summary> 
      CREDUIWIN_CHECKBOX = 0x2, 
      /// <summary> 
      /// Only credential providers that support the authentication package specified by the authPackage parameter should be enumerated. 
      /// This value cannot be combined with CREDUIWIN_IN_CRED_ONLY. 
      /// </summary> 
      CREDUIWIN_AUTHPACKAGE_ONLY = 0x10, 
      /// <summary> 
      /// Only the credentials specified by the InAuthBuffer parameter for the authentication package specified by the authPackage parameter should be enumerated. 
      /// If this flag is set, and the InAuthBuffer parameter is NULL, the function fails. 
      /// This value cannot be combined with CREDUIWIN_AUTHPACKAGE_ONLY. 
      /// </summary> 
      CREDUIWIN_IN_CRED_ONLY = 0x20, 
      /// <summary> 
      /// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only. We recommend that external callers not set this flag. 
      /// </summary> 
      CREDUIWIN_ENUMERATE_ADMINS = 0x100, 
      /// <summary> 
      /// Only the incoming credentials for the authentication package specified by the authPackage parameter should be enumerated. 
      /// </summary> 
      CREDUIWIN_ENUMERATE_CURRENT_USER = 0x200, 
      /// <summary> 
      /// The credential dialog box should be displayed on the secure desktop. This value cannot be combined with CREDUIWIN_GENERIC. 
      /// Windows Vista: This value is not supported until Windows Vista with SP1. 
      /// </summary> 
      CREDUIWIN_SECURE_PROMPT = 0x1000, 
      /// <summary> 
      /// The credential provider should align the credential BLOB pointed to by the refOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system. 
      /// </summary> 
      CREDUIWIN_PACK_32_WOW = 0x10000000, 
     } 
     #endregion 
    } 
} 

-Test

  1. eine neue WPF-Anwendung LoginDialog genannt erstellen.
  2. Lassen Sie eine TextBox in die MainWindow.xaml Datei mit dem Namen textBox1 fallen.
  3. Ersetzen Sie den Code in der Datei MainWindow.xaml.cs.
  4. Lauf!

Beispielausgabe

das Passwort "password" Da, hier ist der Ausgang.

Domain: 
Username: EXAMPLE\fake 
Password (hashed): @@D\a\b\f\n\rgAAAAAU-JPAAAAAAweFpM4nPlOUfKi83JLsl4jjh6nMX34yiH 

Kommentare

Das für WPF funktioniert. Es kann für Silverlight mit der right permissions arbeiten.

Ich weiß nicht, warum jemand jemals dies für legitime benutzerdefinierte Validierung tun würde. Wenn Sie eine Anmeldung für Ihre App erstellen möchten, sollten Sie den Client über SSL (https: //) mit einer ASP.NET-Seite oder einem Webdienst verbinden, der die mit LINQ to SQL bereitgestellten Anmeldeinformationen überprüft. Es kann dann dem Client eine Pass/Fail-Antwort senden.

Oh, und für die Liebe Gottes und alles was heilig ist, salt and hash your users' passwords.

Hinweis: Wenn Sie diesen Login verwenden möchten, um zu verhindern, dass der Nutzer Ihre App ohne Konto/Zahlung verwendet, stehen alle oben genannten Punkte zur Verfügung, reichen jedoch nicht aus, um das Reverse Engineering zu verhindern (z. B. trickst du es in den Gedanken, dass es die Pass-Nachricht erhalten hat). Diese Art von DRM ist ein ganz anderes Ballspiel.

+0

Meine Frage mit einer möglichen Lösung aktualisiert. vielen Dank für Ihre Informationen über Sicherheit, aber diese Frage ist mehr über das Windows-Sicherheitsdialogformular. Ich verwende bereits eine sichere Benutzervalidierung unter anderen Vorsichtsmaßnahmen. – gulbaek

+1

Sie können das unverschlüsselte Passwort erhalten, indem Sie es während des Entpackens 'CredUnPackAuthenticationBuffer (1, ...' – HodlDwon

Verwandte Themen