将SecurePassword绑定到ViewModel

时间:2014-04-11 18:34:24

标签: c# wpf mvvm

我尝试使用自定义SecurePasswordPasswordBox的{​​{1}}属性绑定到我的ViewModel。可悲的是,它无法正常工作。

基本上我在Behavior添加了一个属性,其中包含Behavior的目标属性。

为什么它不起作用的任何想法?

PS:我目前正在回家的路上没有笔记本电脑,我将在大约15分钟内用我的代码更新问题。但如果有人发表想法或某事,那就太好了。

修改

正如我所承诺的,这里有一些代码:)

ViewModel首先:

Behavior

using System; using System.Collections.Generic; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Interactivity; using System.Security; namespace Knerd.Behaviors { public class PasswordChangedBehavior : Behavior<PasswordBox> { protected override void OnAttached() { AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged; base.OnAttached(); } private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) { if (AssociatedObject.Password != null) TargetPassword = AssociatedObject.SecurePassword; } protected override void OnDetaching() { AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged; base.OnDetaching(); } public SecureString TargetPassword { get { return (SecureString)GetValue(TargetPasswordProperty); } set { SetValue(TargetPasswordProperty, value); } } // Using a DependencyProperty as the backing store for TargetPassword. This enables animation, styling, binding, etc... public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString))); } }

PasswordBox

最后,我的<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200"> <i:Interaction.Behaviors> <behaviors:PasswordChangedBehavior TargetPassword="{Binding Password}" /> </i:Interaction.Behaviors> </PasswordBox> 部分。

ViewModel

我希望任何人都能提供帮助,因为我使用的是代码隐藏版本,但我宁愿也不会。

编辑2

实际上不起作用的是,private SecureString password; public SecureString Password { get { return password; } set { if (password != value) { password = value; OnPropertyChanged("Password"); } } } 属性不会更新TargetPassword的属性

2 个答案:

答案 0 :(得分:2)

创建附加属性

public static class PasswordBoxAssistant
{
 public static readonly DependencyProperty BoundPassword =
      DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));

  public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
      "BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));


  private static readonly DependencyProperty UpdatingPassword =
      DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));

  private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
      PasswordBox box = d as PasswordBox;

      // only handle this event when the property is attached to a PasswordBox
      // and when the BindPassword attached property has been set to true
      if (d == null || !GetBindPassword(d))
      {
          return;
      }

      // avoid recursive updating by ignoring the box's changed event
      box.PasswordChanged -= HandlePasswordChanged;

      string newPassword = (string)e.NewValue;

      if (!GetUpdatingPassword(box))
      {
          box.Password = newPassword;
      }

      box.PasswordChanged += HandlePasswordChanged;
  }

  private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
  {
      // when the BindPassword attached property is set on a PasswordBox,
      // start listening to its PasswordChanged event

      PasswordBox box = dp as PasswordBox;

      if (box == null)
      {
          return;
      }

      bool wasBound = (bool)(e.OldValue);
      bool needToBind = (bool)(e.NewValue);

      if (wasBound)
      {
          box.PasswordChanged -= HandlePasswordChanged;
      }

      if (needToBind)
      {
          box.PasswordChanged += HandlePasswordChanged;
      }
  }

  private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
  {
      PasswordBox box = sender as PasswordBox;

      // set a flag to indicate that we're updating the password
      SetUpdatingPassword(box, true);
      // push the new password into the BoundPassword property
      SetBoundPassword(box, box.Password);
      SetUpdatingPassword(box, false);
  }

  public static void SetBindPassword(DependencyObject dp, bool value)
  {
      dp.SetValue(BindPassword, value);
  }

  public static bool GetBindPassword(DependencyObject dp)
  {
      return (bool)dp.GetValue(BindPassword);
  }

  public static string GetBoundPassword(DependencyObject dp)
  {
      return (string)dp.GetValue(BoundPassword);
  }

  public static void SetBoundPassword(DependencyObject dp, string value)
  {
      dp.SetValue(BoundPassword, value);
  }

  private static bool GetUpdatingPassword(DependencyObject dp)
  {
      return (bool)dp.GetValue(UpdatingPassword);
  }

  private static void SetUpdatingPassword(DependencyObject dp, bool value)
  {
      dp.SetValue(UpdatingPassword, value);
  }
}

XAML

<Page xmlns:ff="clr-namespace:FunctionalFun.UI">
<!-- [Snip] -->
  <PasswordBox x:Name="PasswordBox"
      ff:PasswordBoxAssistant.BindPassword="true"  ff:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

</Page>

你可能不想这样做,但如果你真的想继续。

  

WPF / Silverlight PasswordBox没有为Password属性公开DP的原因与安全性有关。   如果WPF / Silverlight要保留DP for Password,则需要框架将密码本身保持在内存中未加密。这被认为是一个非常麻烦的安全攻击媒介。 PasswordBox使用加密内存(各种类型),访问密码的唯一方法是通过CLR属性。

我建议在访问PasswordBox.Password CLR属性时,不要将其放在任何变量中或作为任何属性的值。 将密码以明文形式保存在客户端计算机RAM上是一种安全禁忌。

SecurePassword无法通过绑定完成。

  

.NET文档解释了为什么PasswordBox首先不能绑定。

另一种解决方案是将PasswordBox放入ViewModel公共类LoginViewModel

public class LoginViewModel
{
   // other properties here

   public PasswordBox Password
   {
      get { return m_passwordBox; }
   }

   // Executed when the Login button is clicked.
   private void LoginExecute()
   {
      var password = Password.SecurePassword;

      // do more stuff...
   }
}

是的,您违反了ViewModel的最佳做法,但

  1. 最佳做法是“在大多数情况下都能正常运作的建议” 而不是严格的规则和
  2. 编写简单易读,可维护的代码并避免使用 不必要的复杂性也是那些“最佳实践”规则之一 (可能会被“附属财产”轻微侵犯 解决方法)。

答案 1 :(得分:1)

我想我发现了一种奇怪的解决方案。如果有改进的话请改进:)

我只是改变它:

Behavior

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Interactivity;
using System.Security;

namespace Knerd.Behaviors {
    public class PasswordChangedBehavior : Behavior<PasswordBox> {

        protected override void OnAttached() {
            AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
            base.OnAttached();
        }

        private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e) {
            if (AssociatedObject.SecurePassword != null)
                AssociatedObject.DataContext = AssociatedObject.SecurePassword.Copy();
        }

        protected override void OnDetaching() {
            AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
            base.OnDetaching();
        }

        // Using a DependencyProperty as the backing store for TargetPassword.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetPasswordProperty = DependencyProperty.Register("TargetPassword", typeof(SecureString), typeof(PasswordChangedBehavior), new PropertyMetadata(default(SecureString)));
    }
}

ViewModel根本没有变化,但这是我的View

<PasswordBox Grid.Column="1" Grid.Row="1" Margin="5" Width="300" MinWidth="200" DataContext="{Binding Password, Mode=TwoWay}">
    <i:Interaction.Behaviors>
        <behaviors:PasswordChangedBehavior />
    </i:Interaction.Behaviors>
</PasswordBox>

这样做非常完美,不会暴露明文密码。