PasswordBox コントロールの Password プロパティは仕様のため、バインディングをすることができません。まずもって、バインディングをすることはコンセプト的に非推奨です。
セキュリティ対策の一環のため、バインディングを禁止したのはパスワードの平文をメモリー内に持ちたくありませんでした。PasswordBox は SecurePassword プロパティを追加した経緯があります。SecurePassword は暗号化したパスワードをメモリー内に持つことが目的の機能でした。
なので、SecurePassword とバインディングすればいいのか、となりそうですが、現在は SecurePassword のプロパティ自体、非推奨になっています。
We don't recommend that you use the
SecureString
class for new development. For more information, seeSecureString
shouldn't be used on GitHub.
SecurePassword の実体は SecureString
で実装されていますが、結局以下の問題となります:
- .NET はどこかで文字列をプレーンテキストに変換する必要があるため、完全に防ぐことはできなかった
- .NET Framework 以外では暗号化されません
- .NET Core では、もうメモリー内で暗号化されない
そんなわけで SecurePassword は死んだ仕様となり、簡単にパスワードを使うなら結局 Password プロパティでいいや、ってことになるかどうかは後述のとおりで、今は別の選択ができています。
非推奨でも Password プロパティを Binding する
実際のところ、平文でパスワードを扱わざるをえないなら、もうバインディングしてもいいやってときは、こんな感じでバインディングできると思います。
<PasswordBox local:AttachedProperty.BindablePassword="{Binding Text2}"/>
class AttachedProperty { private static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached( "IsAttached", typeof(bool), typeof(AttachedProperty), new FrameworkPropertyMetadata( false, FrameworkPropertyMetadataOptions.None, (s, e) => { if (s is PasswordBox passwordBox) { if (passwordBox == null) { return; } if ((bool)e.OldValue) { passwordBox.PasswordChanged -= PasswordBox_PasswordChanged; } if ((bool)e.NewValue) { passwordBox.PasswordChanged += PasswordBox_PasswordChanged; } } }) ); private static readonly DependencyProperty BindablePasswordProperty = DependencyProperty.RegisterAttached( "BindablePassword", typeof(string), typeof(AttachedProperty), new FrameworkPropertyMetadata( "", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => { if (s is PasswordBox passwordBox) { var newPassword = (string)e.NewValue; if (GetIsAttached(passwordBox) == false) { SetIsAttached(passwordBox, true); } // 例外 if (string.IsNullOrEmpty(passwordBox.Password) && string.IsNullOrEmpty(newPassword) || passwordBox.Password == newPassword) { return; } passwordBox.PasswordChanged -= PasswordBox_PasswordChanged; passwordBox.Password = newPassword; passwordBox.PasswordChanged += PasswordBox_PasswordChanged; } }) ); public static bool GetIsAttached(DependencyObject d) { return (bool)d.GetValue(IsAttachedProperty); } public static void SetIsAttached(DependencyObject d, bool value) { d.SetValue(IsAttachedProperty, value); } public static string GetBindablePassword(DependencyObject d) { return (string)d.GetValue(BindablePasswordProperty); } public static void SetBindablePassword(DependencyObject d, string value) { d.SetValue(BindablePasswordProperty, value); } private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e) { var passwordBox = sender as PasswordBox; if (passwordBox == null) { return; } SetBindablePassword(passwordBox, passwordBox.Password); } }
Windows Hello を利用した認証
パスワードの認証から Web アカウントマネージャー のような GitHub や Twitter アカウントを利用して認証を受ける方法もあります。(ストア連携が必要になります)
Web アカウントマネージャーよりも楽に使える認証パターンとして、Windows Hello を利用した PIN や指紋認証によるログインを実装することも検討したいです。ログイン設定を OS 管理にできるため、省力化にも繋がると思います。
.NET5 の場合は、UWP (Win RT) から機能をつかうことになります。Visual Studio のプロジェクトファイル .csproj
を開いて以下のように編集します:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework> <UseWPF>true</UseWPF> </PropertyGroup> <ItemGroup> <PackageReference Include="Prism.Unity" Version="8.1.97" /> <PackageReference Include="Prism.Wpf" Version="8.1.97" /> </ItemGroup> </Project>
TargetFramework
のところを編集しただけです。デフォルトだとプロパティ値が net5.0-windows
のようになっていると思います。対応を始める Windows10 のバージョンを明記することで指紋認証に使うクラスが利用できるようになります。
実際に使ってみたサンプルコードはつぎのとおり:
var ucvAvailability = await UserConsentVerifier.CheckAvailabilityAsync(); if (ucvAvailability == UserConsentVerifierAvailability.Available) { var consentResult = await UserConsentVerifier.RequestVerificationAsync("Please provide fingerprint verification."); if (consentResult == UserConsentVerificationResult.Verified) { Debug.WriteLine("OK"); } }
- CheckAvailabilityAsync()
WindowsHello に対応しているかチェックする - RequestVerificationAsync(string)
認証のウィンドウを表示する
非同期を使うので、Prism なんかのコマンドを利用すると楽:
TestFingerPrintCommand = new DelegateCommand(async () =>
{
var ucvAvailability = await UserConsentVerifier.CheckAvailabilityAsync();
...
});
実際に動かしてみた感じはサンプルを参照。
エラー発生するとき
ちなみに、.NET5 で Microsoft.Windows.SDK.Contracts
NuGet パッケージをインストールするとエラーになります。
.NET 5 以上のターゲットを設定する場合、Windows Metadata コンポーネントを直接参照することはできません。 詳細については、 「https://aka.ms/netsdk1130」をご参照ください。
or
SupportedOSPlatformVersion 10.0.xxxxxx.0 を TargetPlatformVersion 7.0 より大きくすることはできません。
上述のとおり、.csproj
を編集してください。
サンプル
GitHub にコードのサンプルを公開しています。