密码框和MVVM

21
我们有以下情况:
  1. MVVM用户界面,用户可以输入密码(实际上是一个PasswordBox
  2. 服务器执行一些工作
  3. 服务器连接到需要认证的某个数据库

我已经阅读了这篇关于MVVM中PasswordBox的问题

但是没有答案可以解决如何处理这种情况!只是强调“千万不要那样做”。

传递密码的正确方法是什么? 如何解决安全问题?

没有合适的方法来绑定PasswordBox, 密码也不应该存储在任何地方,好吧。

那么,MVVM如何处理这种情况呢?

即使模式被打破,有没有好的方法来实现这个功能?

考虑使用Func<string>来检索它,但如果没有绑定,这将变得混乱...

更新 同样适用于从(希望加密的)密码存储初始化PasswordBox。 这难道不是打破MVVM模式吗?我想用户不想每次启动应用程序或要使用数据库时都输入密码。

5个回答

42

就我个人而言,我只是将整个PasswordBox控件传递给我的LoginCommand命令。

我知道这会破坏MVVM模式,因为ViewModel层现在引用了一个与View相关的对象,但我认为在这种特定情况下可以接受。

因此,我的XAML可能看起来像这样:

<Button Content="Login" 
        Command="{Binding LoginCommand}" 
        CommandParameter="{Binding ElementName=MyPasswordBox}" />

还需要一个LoginCommand,它的功能类似于这样:

private void Login(object obj)
{
    PasswordBox pwBox = obj as PasswordBox;

    SomeBlackBoxClass.ValidatePassword(UserName, pwBox.Password);
}

我想你也可以对该值运行某种加密算法,然后将该值的哈希值与用户密码的哈希值进行比较。
private void Login(object obj)
{
    PasswordBox pwBox = obj as PasswordBox;
    var encryptedPassword = SomeLibrary.EncryptValue(pwBox.Password, someKey);

    if (encryptedPassword == User.EncryptedPassword)
        // Success
}

我不是密码框控件或安全方面的专家,但我知道你不希望在应用程序内存的任何地方以明文形式存储用户的密码。

(技术上,它以明文形式存储在 PasswordBox.Password 中-如果您想要验证,可以使用类似于 Snoop 的工具-但通常 PasswordBox 存在的时间不会超过用户登录所需的时间,并且实际上“密码”只是用户输入的文本,可能正确也可能不正确。一个键盘记录器可以获取相同的信息。)


那我真的要将一个PasswordBox交给服务器吗? - Mare Infinitus
1
@MareInfinitus 个人而言,我会在客户端加密 PasswordBox.Password,然后将用户名和加密后的密码传递给服务器,而不是将未加密的 PasswordBox 对象传递给服务器。PasswordBox.Password 是明文,所以我不喜欢将 PasswordBox 对象保留更长时间。 - Rachel
@MareInfinitus 我见过的允许你“记住密码”以免每次都输入的系统,都会在本地某个地方存储加密后的密码,例如在注册表中,并将加密值与用户表中的加密密码进行比较。 - Rachel
希望有人能提供一个真正的解决方案,但你的解决方案是可行的!谢谢! - Mare Infinitus
那么你是将控件发送到ViewModel中吗?这不是一个好主意吗? - Carlo
显示剩余2条评论

7
我已经通过创建一个公开SecureString依赖属性的UserControl来解决了这个问题,可以进行绑定。 这种方法始终将密码保存在SecureString中,并且不会“破坏”MVVM。

UserControl

XAML

<UserControl x:Class="Example.PasswordUserControl"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         mc:Ignorable="d"
         d:DesignHeight="300" d:DesignWidth="300">
    <Grid>       
        <PasswordBox Name="PasswordBox" />
    </Grid>
</UserControl>

CS

public partial class PasswordUserControl : UserControl
{
    public SecureString Password
    {
        get { return (SecureString) GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(UserCredentialsInputControl),
            new PropertyMetadata(default(SecureString)));


    public PasswordUserControl()
    {
        InitializeComponent();

        // Update DependencyProperty whenever the password changes
        PasswordBox.PasswordChanged += (sender, args) => {
            Password = ((PasswordBox) sender).SecurePassword;
        };
    }
}

示例用法

使用该控件非常简单,只需将控件上的密码DependencyProperty绑定到ViewModel上的Password属性即可。ViewModel的Password属性应为SecureString类型。

<controls:PasswordUserControl Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

根据您的需要,更改绑定上的模式和UpdateSource触发器。

如果您需要以明文形式获取密码,请参考以下页面描述将SecureString和string之间进行正确转换的方法:http://blogs.msdn.com/b/fpintos/archive/2009/06/12/how-to-properly-convert-securestring-to-string.aspx。当然您不应该存储明文字符串...


这可能是最好的解决方案,但由于代码示例不完整,它是无用的... - Aistis Taraskevicius
1
嗨,Aistis,你能否解释一下为什么你认为这个示例不完整?我认为我已经提供了足够的信息来解决问题并实现解决方案,但如果你告诉我它缺少哪些内容,我很乐意更新原始帖子。 - DinoM
1
嘿 @DinoM,UserCredentialsInputControl 是什么意思?我得到了类型引用丢失的错误..也许是因为我在 .net core 中使用它。你能确认一下吗? - David Silwal

1
根据你对mvvm的理解(在我的方式中,在某些情况下允许使用code behind),我创建了一个PasswordBox和一个命名为TextBlock的控件。
Xaml
<PasswordBox Height="23" Width="156" PasswordChar="*" PasswordChanged="pwBoxUser_PasswordChanged"/>
<TextBlock Height="1" Width="1" Name="MD5pw" Text="{Binding Passwort, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" VerticalAlignment="Top" />

代码后端
    private void pwBoxUser_PasswordChanged(object sender, RoutedEventArgs e)
    {
        var pBox =sender as PasswordBox;
        string blank=pBox.Password;

        //to crypt my blank Password
        var sMD5 = myMD5.toMD5(blank); //implement your crypt logic here
        blank ="";

        MD5pw.Text = sMD5;
    }

就像你所看到的,你的密码是安全的,而且你可以轻松地绑定它。


现在您在PasswordBoxEdit中有一个普通文本密码,在TextBlock中有一个加密的密码。这样做会使事情更安全吗? - ProfK
它既不会让事情变得更糟,也不会让事情变得更糟,你可以绑定到它,而且你不需要交出你的 PasswordBox,这比 Rachel 的版本更符合 MVVM,但这仍然取决于你,两种解决方案都有效且安全。 - WiiMaxx
是的,请原谅。我是WPF的新手,我不知道Password不能绑定,因为它不是一个依赖属性。 - ProfK

0

谢谢您的回答。但我真的想要安全性! - Mare Infinitus

0

在我看来,DinoM提出的解决方案比Chandramouleswaran Ravichandra提供的两个链接中使用的'Passwordhelper'类更加优雅和易于理解(但所有功劳都归功于Samuel Jack在他的博客http://blog.functionalfun.net/2008/06/wpf-passwordbox-and-data-binding.html中提出的想法)

对于那些由于缺少“UserCredentialsInputControl”类型而无法使DinoM的解决方案工作的人,只需将此参数更改为typeof(PasswordUserControl),它就可以工作了。

SingletonSean在他的Youtube频道上有一个名为;

绑定到PasswordBox(MVVM) - 简单的WPF(.NET CORE)https://www.youtube.com/watch?v=G9niOcc5ssw

这是我找到的最好的源代码示例,因为它是一个完整的示例,但他使用字符串而不是SecureString作为PasswordProperty的基本类型,但这很容易修复。他还在GitHub存储库中提供了视频源代码的链接。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接