2012-04-20 16 views
7

Estoy creando una aplicación Xaml/C# y me gustaría que aparezca con una Petición de inicio de sesión.Seguridad de Windows Validación de inicio de sesión personalizado

Me gustaría saber si es posible utilizar CredUIPromptForWindowsCredentials.

  • Mostrar diálogo de seguridad de Windows
  • Obtener el nombre de usuario introducido la contraseña &
  • Realizar la validación personalizada
  • Si éxito de validación -> continuar aplicación
  • más si falla la validación -> -Informar usuario de nombre de usuario no válido o contraseña

Ya he visto Windows Security login form? y http://www.pinvoke.net/default.aspx/credui/creduipromptforwindowscredentials.html?diff=y pero no explican cómo manejar la validación.

Realmente me gustaría un pequeño ejemplo, donde si el usuario ingresa username = "Bo" y password = "123", entonces muestra otro mensaje de error y permite al usuario volver a intentarlo.

La aplicación se instalará en varias computadoras.

¿O simplemente no es posible?

actualización

Inspirado por la respuesta a esta pregunta en Show Authentication dialog in C# for windows Vista/7

he modificado el código para funcionar como se espera.

Por favor no, que la parte de validación es solo como prueba de concepto.

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

Creo que es mejor crear un formulario personalizado para esto. Fácil de administrar y menos complicado. –

+1

De hecho, ya tengo una forma personalizada, solo quiero ver si es posible usar Windows propio. Además, el mío no es el que mejor se ve :-) – gulbaek

+0

Y por "no es el que mejor se ve", te refieres a que no se asemeja al diálogo de seguridad de Windows lo suficiente como para engañar al usuario. – SPE

Respuesta

4

Encontrará una implementación completa para WPF y WinForms usando CredUIPromptForWindowsCredentials en Ookii dialogs.

3

estaba un poco horrorizado cuando empecé a pensar que esto podría ser posible.

La respuesta es sí y no. Puede obtener el dominio de la red y el nombre de usuario, pero (gracias a Dios), no puede obtener la contraseña real, solo un hash de la contraseña.

Tomando grandes cantidades del PInvoke, aquí hay una aplicación WPF de ejemplo que trae y saca el nombre de usuario y la contraseña.

Código

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

prueba

  1. Crear una nueva aplicación de WPF denominado LoginDialog.
  2. Coloque un TextBox en el archivo MainWindow.xaml proporcionado con el nombre textBox1.
  3. Reemplace el código en el archivo MainWindow.xaml.cs.
  4. Ejecutar!

Ejemplo de salida

Dada la contraseña "contraseña", aquí está la salida.

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

Comentarios

Esto funciona para WPF. Puede funcionar para Silverlight con el right permissions.

No sé por qué alguien haría esto para la validación personalizada legítima. Si desea crear un inicio de sesión para su aplicación, sugiero que el cliente se conecte a través de SSL (https: //) a una página ASP.NET o servicio web que verificará las credenciales proporcionadas mediante LINQ to SQL. A continuación, puede enviar al cliente una respuesta de aprobado/reprobado.

Oh, y por el amor de dios y todo lo que es santo, salt and hash your users' passwords.

Nota: Si desea utilizar este inicio de sesión para evitar que el usuario use su aplicación sin tener una cuenta/pagar, todo lo anterior es válido, pero no será suficiente para evitar que la gente realice ingeniería inversa y crackear la aplicación (por ejemplo, engañándolo para que piense que ha recibido el mensaje de pase). Ese tipo de DRM es un juego de pelota más completo.

+0

Actualicé mi pregunta con una posible solución. Gracias por compartir su información sobre seguridad, pero esta pregunta se trata más acerca de cómo usar el formulario de diálogo de seguridad de Windows. Ya estoy usando una validación de usuario segura, entre otras precauciones. – gulbaek

+1

Puede obtener la contraseña sin hash descifrándola durante el desempaquetado 'CredUnPackAuthenticationBuffer (1, ...' – HodlDwon

Cuestiones relacionadas