2012-04-20 11 views
7

私はXaml/C#アプリケーションを作成しており、ログインプロンプトをポップアップ表示したいと思います。Windowsセキュリティカスタムログインの検証

私はCredUIPromptForWindowsCredentialsを使用できるかどうかを知りたいと思います。

  • ショーWindowsセキュリティ]ダイアログ
  • 入力されたユーザ名に&パスワードを取得します
  • 検証大成功した場合、カスタム検証
  • を実行 - 無効なユーザー名の> -informユーザー - >検証が失敗した場合、他の
  • アプリ続けますまたはパスワード

私はすでにWindows Security login form?とを見ていますを参照してください。ただし、検証の処理方法については説明していません。

ユーザーがusername = "Bo"とpassword = "123"を入力すると、エラーメッセージが表示され、再度試してみるという小さな例が本当に好きです。

アプリケーションは複数のコンピュータにインストールされます。

これは簡単ではありませんか?私は期待どおりに動作するようにコードを変更したこの問題の答えShow Authentication dialog in C# for windows Vista/7

に触発さ

更新

検証部分は概念証明のためのものではありません。

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

私はそのためにカスタムフォームを作るのが良いと思います。管理が簡単で、複雑さも少なくて済みます。 –

+1

私は実際にはすでに独自のフォームを持っています。また、私の最高の探していない:-) – gulbaek

+0

と "最高の探していない"によって、あなたはそれを十分にWindowsのセキュリティのダイアログに似ていないユーザーをだますことを意味します。 – SPE

答えて

4

Ookii dialogsにCredUipromptForWindowsCredentialsを使用して、WPFとWinFormsの完全な実装を見つけることができます。

3

私はこれが可能であるかもしれない考え始めたとき、私は少しぞっとしました。

答えは「はい」です。あなたはネットワークドメインとユーザー名を保持することができますが、(本当にうれしい)、実際のパスワードを保持することはできません。パスワードのハッシュだけです。

PInvokeから大量に借りると、ユーザー名とパスワードを持ち込んで出力するサンプルWPF Appがあります。

コード

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

テスト

  1. LoginDialogと呼ばれる新しいWPFアプリケーションを作成します。
  2. textBox1という名前のMainWindow.xamlファイルにTextBoxを入れてください。
  3. MainWindow.xaml.csファイルのコードを置き換えます。
  4. 実行!

パスワード "パスワード" を考えるとサンプル出力

は、ここに出力されます。

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

コメント

これは、WPFのために働きます。それはright permissionsとSilverlightのために働くことができます。

正当なカスタム検証のためにこれを行う理由はわかりません。あなたのアプリケーションのログインを作成する場合は、クライアントをSSL(https://)経由でLINQ to SQLを使用して提供された資格情報をチェックするASP.NETページまたはWebサービスに接続することをお勧めします。その後、クライアントに合格/不合格の応答を送信することができます。

ああ、神の愛と聖なるすべてのために、salt and hash your users' passwords

注:このログインを使用して、アカウント/支払いを行わずにアプリを使用できないようにするには、上記のすべてが成立していますが、リバースエンジニアリングやアプリのクラッキング(例えば、それがパスメッセージを受け取ったと考えるようにそれをすること)。 DRMのようなものは、全体的な「野球」のゲームです。

+0

私の質問を可能な解決策で更新しました。セキュリティに関する情報をお寄せいただきありがとうございますが、この質問はWindowsのセキュリティダイアログフォームを使用する方法についての詳細です。私はすでに他の予防措置の中でも安全なユーザー確認を使用しています。 – gulbaek

+1

ハッシュされていないパスワードを取得するには、 'CredUnPackAuthenticationBuffer(1、...' – HodlDwon

関連する問題