mar*_*ith 243 c# passwords wpf mvvm wpf-controls
我遇到了绑定到PasswordBox的问题.这似乎是一个安全风险,但我正在使用MVVM模式,所以我希望绕过这个.我在这里找到了一些有趣的代码(有没有人用过这个或类似的东西?)
http://www.wpftutorial.net/PasswordBox.html
它在技术上看起来很棒,但我不确定如何检索密码.
我基本上有我的LoginViewModel
for Username
和属性Password
.Username
很好,正在工作,因为它是一个TextBox
.
我按照说明使用了上面的代码并输入了这个
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
Run Code Online (Sandbox Code Playgroud)
当我有了PasswordBox
a TextBox
,Binding Path=Password
然后我的房产LoginViewModel
被更新了.
我的代码很简单,基本上我有一个Command
for my Button
.当我按下它时会CanLogin
被调用,如果它返回true,则调用它Login
.
你可以看到我检查我的房产在Username
这里工作得很好.
在Login
我一起发送到我的服务Username
和Password
,Username
包含数据从我View
却Password
是Null|Empty
private DelegateCommand loginCommand;
public string Username { get; set; }
public string Password { get; set; }
public ICommand LoginCommand
{
get
{
if (loginCommand == null)
{
loginCommand = new DelegateCommand(
Login, CanLogin );
}
return loginCommand;
}
}
private bool CanLogin()
{
return !string.IsNullOrEmpty(Username);
}
private void Login()
{
bool result = securityService.IsValidLogin(Username, Password);
if (result) { }
else { }
}
Run Code Online (Sandbox Code Playgroud)
这就是我在做的事情
<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
MinWidth="180" />
<PasswordBox ff:PasswordHelper.Attach="True"
ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>
Run Code Online (Sandbox Code Playgroud)
我有我的TextBox
,这没问题,但我ViewModel
的Password
是空的.
我做错了什么或错过了一步?
我放了一个断点,确实代码进入了静态助手类,但它永远不会更新我Password
的ViewModel
.
Kon*_*man 187
我的2美分:
我使用WPF和MVVM开发了一个典型的登录对话框(用户和密码框,加上"确定"按钮).我通过简单地将PasswordBox控件本身作为参数传递给附加到"确定"按钮的命令来解决了密码绑定问题.所以我认为:
<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
CommandParameter="{Binding ElementName=txtPassword}"/>
Run Code Online (Sandbox Code Playgroud)
在ViewModel中,Execute
附加命令的方法如下:
void Execute(object parameter)
{
var passwordBox = parameter as PasswordBox;
var password = passwordBox.Password;
//Now go ahead and check the user name and password
}
Run Code Online (Sandbox Code Playgroud)
这略微违反了MVVM模式,因为现在ViewModel知道View是如何实现的,但在那个特定的项目中我能负担得起.希望它对某人也有用.
Ste*_* CO 174
也许我错过了一些东西,但似乎大多数这些解决方案都过于复杂化并取消了安全实践.
此方法不违反MVVM模式并保持完整的安全性.是的,从技术上来说它是代码背后,但它只不过是一个"特殊情况"绑定.ViewModel仍然不了解View实现,如果您尝试将PasswordBox传递给ViewModel,我认为这样做.
Code Behind!=自动MVVM违规.这一切都取决于你用它做什么.在这种情况下,我们只是手动编码绑定,因此它都被认为是UI实现的一部分,因此是可以的.
在ViewModel中,只是一个简单的属性.我把它设为"只写",因为不应该出于任何原因从ViewModel外部检索它,但它不一定是这样.请注意,它是一个SecureString,而不仅仅是一个字符串.
public SecureString SecurePassword { private get; set; }
Run Code Online (Sandbox Code Playgroud)
在xaml中,您设置了一个PasswordChanged事件处理程序.
<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>
Run Code Online (Sandbox Code Playgroud)
在后面的代码中:
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}
Run Code Online (Sandbox Code Playgroud)
使用此方法,您的密码始终保留在SecureString中,因此可提供最大的安全性.如果您真的不关心安全性,或者需要明文密码来获取需要它的下游方法(注意:大多数需要密码的.NET方法也支持SecureString选项,因此您可能不需要明文密码即使你认为你这样做,你也可以使用Password属性.像这样:
(ViewModel属性)
public string Password { private get; set; }
Run Code Online (Sandbox Code Playgroud)
(代码背后)
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}
Run Code Online (Sandbox Code Playgroud)
如果你想保持强类型,你可以用ViewModel的界面替换(动态)强制转换.但实际上,"正常"的数据绑定也不是强类型的,所以它并不是那么重要.
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
if (this.DataContext != null)
{ ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}
Run Code Online (Sandbox Code Playgroud)
所以世界上最好的 - 你的密码是安全的,你的ViewModel只有一个属性,就像任何其他属性一样,你的View是自包含的,不需要外部引用.
Jus*_*gel 162
对不起,但你做错了.
人们应该在眼睑内侧纹上以下安全指南:
切勿将纯文本密码保存在内存中.
WPF/Silverlight PasswordBox不为Password属性公开DP的原因与安全性有关.
如果WPF/Silverlight要保留DP for Password,则需要框架将密码本身保持在内存中未加密.这被认为是一个非常麻烦的安全攻击媒介.PasswordBox使用加密内存(各种),访问密码的唯一方法是通过CLR属性.
我建议在访问PasswordBox.Password CLR属性时,不要将其放在任何变量或任何属性的值中.
在客户机RAM上以明文形式保存密码是一种安全禁忌.
所以摆脱那个"公共字符串密码{get; set;}"你已经到了那里.
访问PasswordBox.Password时,只需将其取出并尽快将其发送到服务器.不要保留密码的值,也不要像对待任何其他客户端机器文本那样对待它.不要在内存中保留明文密码.
我知道这打破了MVVM模式,但你不应该绑定到PasswordBox.Password附加DP,将你的密码存储在ViewModel或任何其他类似的恶作剧中.
如果您正在寻找过度架构的解决方案,请
参阅以下内容:1.使用一种返回密码明文的方法创建IHavePassword接口.
2.让UserControl实现IHavePassword接口.
3.在您的IoC中注册UserControl实例,以实现IHavePassword接口.
4.当需要密码的服务器请求发生时,请调用IoC以获取IHavePassword实现,而不是获得令人垂涎的密码.
只是我对它的看法.
- 贾斯汀
小智 20
你可以使用这个XAML:
<PasswordBox Name="PasswordBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PasswordChanged">
<i:InvokeCommandAction Command="{Binding PasswordChangedCommand}" CommandParameter="{Binding ElementName=PasswordBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</PasswordBox>
Run Code Online (Sandbox Code Playgroud)
而这个命令执行方法:
private void ExecutePasswordChangedCommand(PasswordBox obj)
{
if (obj != null)
Password = obj.Password;
}
Run Code Online (Sandbox Code Playgroud)
Vla*_*kov 14
这对我来说很好.
<Button Command="{Binding Connect}"
CommandParameter="{Binding ElementName=MyPasswordBox}"/>
Run Code Online (Sandbox Code Playgroud)
Jan*_*m B 10
不违反MVVM模式的简单解决方案是在ViewModel中引入一个收集密码的事件(或委托).
在ViewModel中:
public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;
使用这些EventArgs:
class HarvestPasswordEventArgs : EventArgs
{
public string Password;
}
Run Code Online (Sandbox Code Playgroud)
在View中,订阅创建ViewModel的事件并填写密码值.
_viewModel.HarvestPassword += (sender, args) =>
args.Password = passwordBox1.Password;
Run Code Online (Sandbox Code Playgroud)
在ViewModel中,当您需要密码时,您可以触发事件并从那里获取密码:
if (HarvestPassword == null)
//bah
return;
var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);
LoginHelpers.Login(Username, pwargs.Password);
Run Code Online (Sandbox Code Playgroud)
using System.Windows;
using System.Windows.Controls;
namespace CustomControl
{
public class BindablePasswordBox : Decorator
{
/// <summary>
/// The password dependency property.
/// </summary>
public static readonly DependencyProperty PasswordProperty;
private bool isPreventCallback;
private RoutedEventHandler savedCallback;
/// <summary>
/// Static constructor to initialize the dependency properties.
/// </summary>
static BindablePasswordBox()
{
PasswordProperty = DependencyProperty.Register(
"Password",
typeof(string),
typeof(BindablePasswordBox),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPasswordPropertyChanged))
);
}
/// <summary>
/// Saves the password changed callback and sets the child element to the password box.
/// </summary>
public BindablePasswordBox()
{
savedCallback = HandlePasswordChanged;
PasswordBox passwordBox = new PasswordBox();
passwordBox.PasswordChanged += savedCallback;
Child = passwordBox;
}
/// <summary>
/// The password dependency property.
/// </summary>
public string Password
{
get { return GetValue(PasswordProperty) as string; }
set { SetValue(PasswordProperty, value); }
}
/// <summary>
/// Handles changes to the password dependency property.
/// </summary>
/// <param name="d">the dependency object</param>
/// <param name="eventArgs">the event args</param>
private static void OnPasswordPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs eventArgs)
{
BindablePasswordBox bindablePasswordBox = (BindablePasswordBox) d;
PasswordBox passwordBox = (PasswordBox) bindablePasswordBox.Child;
if (bindablePasswordBox.isPreventCallback)
{
return;
}
passwordBox.PasswordChanged -= bindablePasswordBox.savedCallback;
passwordBox.Password = (eventArgs.NewValue != null) ? eventArgs.NewValue.ToString() : "";
passwordBox.PasswordChanged += bindablePasswordBox.savedCallback;
}
/// <summary>
/// Handles the password changed event.
/// </summary>
/// <param name="sender">the sender</param>
/// <param name="eventArgs">the event args</param>
private void HandlePasswordChanged(object sender, RoutedEventArgs eventArgs)
{
PasswordBox passwordBox = (PasswordBox) sender;
isPreventCallback = true;
Password = passwordBox.Password;
isPreventCallback = false;
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了在不破坏MVVM的情况下解决OP问题,我将使用自定义值转换器和必须从密码框中检索的值(密码)的包装器.
public interface IWrappedParameter<T>
{
T Value { get; }
}
public class PasswordBoxWrapper : IWrappedParameter<string>
{
private readonly PasswordBox _source;
public string Value
{
get { return _source != null ? _source.Password : string.Empty; }
}
public PasswordBoxWrapper(PasswordBox source)
{
_source = source;
}
}
public class PasswordBoxConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// Implement type and value check here...
return new PasswordBoxWrapper((PasswordBox)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException("No conversion.");
}
}
Run Code Online (Sandbox Code Playgroud)
在视图模型中:
public string Username { get; set; }
public ICommand LoginCommand
{
get
{
return new RelayCommand<IWrappedParameter<string>>(password => { Login(Username, password); });
}
}
private void Login(string username, string password)
{
// Perform login here...
}
Run Code Online (Sandbox Code Playgroud)
因为视图模型使用IWrappedParameter<T>
,所以不需要任何关于PasswordBoxWrapper
nor的知识PasswordBoxConverter
.这样,您可以将PasswordBox
对象与视图模型隔离,而不会破坏MVVM模式.
在视图中:
<Window.Resources>
<h:PasswordBoxConverter x:Key="PwdConverter" />
</Window.Resources>
...
<PasswordBox Name="PwdBox" />
<Button Content="Login" Command="{Binding LoginCommand}"
CommandParameter="{Binding ElementName=PwdBox, Converter={StaticResource PwdConverter}}" />
Run Code Online (Sandbox Code Playgroud)
小智 6
这种实现略有不同.您将密码框传递给View通过ViewModel中的属性绑定,它不使用任何命令参数.ViewModel保持对视图的无知.我有一个VB vs 2010项目,可以从SkyDrive下载.Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511
我在Wpf MvvM应用程序中使用PasswordBox的方式非常简单,对我来说效果很好.这并不意味着我认为这是正确的方式或最好的方式.它只是使用PasswordBox和MvvM模式的一个实现.
基本上您创建一个公共只读属性,View可以将其绑定为PasswordBox(实际控件)示例:
Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
Get
If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
Return _thePassWordBox
End Get
End Property
Run Code Online (Sandbox Code Playgroud)
我使用后备字段来执行属性的自我初始化.
然后从Xaml绑定ContentControl的内容或控件容器示例:
<ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />
Run Code Online (Sandbox Code Playgroud)
从那里你可以完全控制密码箱我还使用PasswordAccessor(只是字符串函数)在登录时或其他任何你想要密码的地方返回密码值.在示例中,我在通用用户对象模型中有一个公共属性.例:
Public Property PasswordAccessor() As Func(Of String)
Run Code Online (Sandbox Code Playgroud)
在用户对象中,密码字符串属性是只读的,没有任何后备存储,它只是从PasswordBox返回密码.例:
Public ReadOnly Property PassWord As String
Get
Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
End Get
End Property
Run Code Online (Sandbox Code Playgroud)
然后在ViewModel中,我确保创建了Accessor并将其设置为PasswordBox.Password属性'示例:
Public Sub New()
'Sets the Accessor for the Password Property
SetPasswordAccessor(Function() ThePassWordBox.Password)
End Sub
Friend Sub SetPasswordAccessor(ByVal accessor As Func(Of String))
If Not IsNothing(VMUser) Then VMUser.PasswordAccessor = accessor
End Sub
Run Code Online (Sandbox Code Playgroud)
当我需要密码字符串说登录时,我只是获得真正调用函数来获取密码并将其返回的用户对象密码属性,然后用户对象不存储实际密码.示例:将在ViewModel中
Private Function LogIn() as Boolean
'Make call to your Authentication methods and or functions. I usally place that code in the Model
Return AuthenticationManager.Login(New UserIdentity(User.UserName, User.Password)
End Function
Run Code Online (Sandbox Code Playgroud)
应该这样做.ViewModel不需要任何View的控件知识.视图仅绑定到ViewModel中的属性,与视图绑定到图像或其他资源没有任何区别.在这种情况下,资源(Property)恰好是用户控件.它允许在ViewModel创建和拥有Property时进行测试,并且Property独立于View.至于安全性,我不知道这个实现有多好.但是通过使用Function,Value不会存储在Property所访问的Property本身中.
我花了大量时间来研究各种解决方案。我不喜欢装饰器的想法,行为使验证UI混乱,背后的代码...真的吗?
最好的方法是坚持使用自定义附加属性并SecureString
在视图模型中绑定到您的属性。尽可能将其保留在那里。每当您需要快速访问纯密码时,请使用以下代码将其临时转换为不安全的字符串:
namespace Namespace.Extensions
{
using System;
using System.Runtime.InteropServices;
using System.Security;
/// <summary>
/// Provides unsafe temporary operations on secured strings.
/// </summary>
[SuppressUnmanagedCodeSecurity]
public static class SecureStringExtensions
{
/// <summary>
/// Converts a secured string to an unsecured string.
/// </summary>
public static string ToUnsecuredString(this SecureString secureString)
{
// copy&paste from the internal System.Net.UnsafeNclNativeMethods
IntPtr bstrPtr = IntPtr.Zero;
if (secureString != null)
{
if (secureString.Length != 0)
{
try
{
bstrPtr = Marshal.SecureStringToBSTR(secureString);
return Marshal.PtrToStringBSTR(bstrPtr);
}
finally
{
if (bstrPtr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(bstrPtr);
}
}
}
return string.Empty;
}
/// <summary>
/// Copies the existing instance of a secure string into the destination, clearing the destination beforehand.
/// </summary>
public static void CopyInto(this SecureString source, SecureString destination)
{
destination.Clear();
foreach (var chr in source.ToUnsecuredString())
{
destination.AppendChar(chr);
}
}
/// <summary>
/// Converts an unsecured string to a secured string.
/// </summary>
public static SecureString ToSecuredString(this string plainString)
{
if (string.IsNullOrEmpty(plainString))
{
return new SecureString();
}
SecureString secure = new SecureString();
foreach (char c in plainString)
{
secure.AppendChar(c);
}
return secure;
}
}
}
Run Code Online (Sandbox Code Playgroud)
确保您允许GC收集您的UI元素,因此不要再对上的PasswordChanged
事件使用静态事件处理程序PasswordBox
。我还发现了一个异常,其中使用SecurePassword
属性进行设置时控件未更新UI,这就是我将密码复制到其中的原因Password
。
namespace Namespace.Controls
{
using System.Security;
using System.Windows;
using System.Windows.Controls;
using Namespace.Extensions;
/// <summary>
/// Creates a bindable attached property for the <see cref="PasswordBox.SecurePassword"/> property.
/// </summary>
public static class PasswordBoxHelper
{
// an attached behavior won't work due to view model validation not picking up the right control to adorn
public static readonly DependencyProperty SecurePasswordBindingProperty = DependencyProperty.RegisterAttached(
"SecurePassword",
typeof(SecureString),
typeof(PasswordBoxHelper),
new FrameworkPropertyMetadata(new SecureString(),FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, AttachedPropertyValueChanged)
);
private static readonly DependencyProperty _passwordBindingMarshallerProperty = DependencyProperty.RegisterAttached(
"PasswordBindingMarshaller",
typeof(PasswordBindingMarshaller),
typeof(PasswordBoxHelper),
new PropertyMetadata()
);
public static void SetSecurePassword(PasswordBox element, SecureString secureString)
{
element.SetValue(SecurePasswordBindingProperty, secureString);
}
public static SecureString GetSecurePassword(PasswordBox element)
{
return element.GetValue(SecurePasswordBindingProperty) as SecureString;
}
private static void AttachedPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// we'll need to hook up to one of the element's events
// in order to allow the GC to collect the control, we'll wrap the event handler inside an object living in an attached property
// don't be tempted to use the Unloaded event as that will be fired even when the control is still alive and well (e.g. switching tabs in a tab control)
var passwordBox = (PasswordBox)d;
var bindingMarshaller = passwordBox.GetValue(_passwordBindingMarshallerProperty) as PasswordBindingMarshaller;
if (bindingMarshaller == null)
{
bindingMarshaller = new PasswordBindingMarshaller(passwordBox);
passwordBox.SetValue(_passwordBindingMarshallerProperty, bindingMarshaller);
}
bindingMarshaller.UpdatePasswordBox(e.NewValue as SecureString);
}
/// <summary>
/// Encapsulated event logic
/// </summary>
private class PasswordBindingMarshaller
{
private readonly PasswordBox _passwordBox;
private bool _isMarshalling;
public PasswordBindingMarshaller(PasswordBox passwordBox)
{
_passwordBox = passwordBox;
_passwordBox.PasswordChanged += this.PasswordBoxPasswordChanged;
}
public void UpdatePasswordBox(SecureString newPassword)
{
if (_isMarshalling)
{
return;
}
_isMarshalling = true;
try
{
// setting up the SecuredPassword won't trigger a visual update so we'll have to use the Password property
_passwordBox.Password = newPassword.ToUnsecuredString();
// you may try the statement below, however the benefits are minimal security wise (you still have to extract the unsecured password for copying)
//newPassword.CopyInto(_passwordBox.SecurePassword);
}
finally
{
_isMarshalling = false;
}
}
private void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
{
// copy the password into the attached property
if (_isMarshalling)
{
return;
}
_isMarshalling = true;
try
{
SetSecurePassword(_passwordBox, _passwordBox.SecurePassword.Copy());
}
finally
{
_isMarshalling = false;
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
和XAML用法:
<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
Run Code Online (Sandbox Code Playgroud)
我在视图模型中的属性如下所示:
[RequiredSecureString]
public SecureString LogonPassword
{
get
{
return _logonPassword;
}
set
{
_logonPassword = value;
NotifyPropertyChanged(nameof(LogonPassword));
}
}
Run Code Online (Sandbox Code Playgroud)
该RequiredSecureString
仅仅是一个拥有以下逻辑简单的自定义的验证:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true)]
public class RequiredSecureStringAttribute:ValidationAttribute
{
public RequiredSecureStringAttribute()
:base("Field is required")
{
}
public override bool IsValid(object value)
{
return (value as SecureString)?.Length > 0;
}
}
Run Code Online (Sandbox Code Playgroud)
这边有 完整且经过测试的纯MVVM解决方案。
虽然我同意避免将密码存储在任何地方很重要,但我仍然需要能够在没有视图的情况下实例化视图模型并执行我的测试.
对我有用的解决方案是将PasswordBox.Password函数注册到视图模型,并让视图模型在执行登录代码时调用它.
这确实意味着视图的代码隐藏中的一行代码.
所以,在我的Login.xaml中,我有
<PasswordBox x:Name="PasswordBox"/>
Run Code Online (Sandbox Code Playgroud)
在Login.xaml.cs我有
LoginViewModel.PasswordHandler = () => PasswordBox.Password;
Run Code Online (Sandbox Code Playgroud)
然后在LoginViewModel.cs中我定义了PasswordHandler
public Func<string> PasswordHandler { get; set; }
Run Code Online (Sandbox Code Playgroud)
当登录需要发生时,代码调用处理程序从视图中获取密码......
bool loginResult = Login(Username, PasswordHandler());
Run Code Online (Sandbox Code Playgroud)
这样,当我想测试viewmodel时,我可以简单地将PasswordHandler设置为匿名方法,该方法允许我提供我想在测试中使用的任何密码.
归档时间: |
|
查看次数: |
183129 次 |
最近记录: |