[C#] MVVM에서 PasswordBox에 바인딩하는 방법

P에 바인딩하는 데 문제가 asswordBox있습니다. 보안 위험 인 것 같지만 MVVM 패턴을 사용하고 있으므로 이것을 무시하고 싶습니다. 여기서 흥미로운 코드를 찾았습니다 (누구나 이와 비슷한 것을 사용 했습니까?)

http://www.wpftutorial.net/PasswordBox.html

기술적으로는 훌륭해 보이지만 암호를 검색하는 방법을 잘 모르겠습니다.

나는 기본적으로 LoginViewModelfor Username및에 속성이 있습니다 Password. Username괜찮습니다 TextBox.

명시된대로 위의 코드를 사용하고 이것을 입력했습니다.

<PasswordBox ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

내가 있었을 때 PasswordBoxA와를 TextBox하고 Binding Path=Password그 다음 내에서 속성 LoginViewModel업데이트되었습니다.

내 코드는 기본적으로 나는이 매우 간단 Command내을 위해 Button. 를 누르면 CanLogin호출되고 true를 반환하면 호출 Login됩니다.
당신은 내가 Username잘 작동 하는 여기에 내 재산을 확인 볼 수 있습니다 .

에서 Login내 서비스 A에 따라 전송 Username하고 Password, Username데이터를 포함 내 ViewPassword입니다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 { }
}

이것이 내가하고있는 일입니다.

<TextBox Text="{Binding Path=Username, UpdateSourceTrigger=PropertyChanged}"
         MinWidth="180" />

<PasswordBox ff:PasswordHelper.Attach="True"
             ff:PasswordHelper.Password="{Binding Path=Password}" Width="130"/>

내가 TextBox이 아무 문제가 없지만, 내에서 ViewModel(가) Password비어 있습니다.

내가 잘못했거나 단계를 놓치고 있습니까?

중단 점을 넣고 코드가 정적 도우미 클래스에 충분히 들어 가지 만 내 my Password를 업데이트하지는 않습니다 ViewModel.



답변

죄송하지만 잘못하고 있습니다.

사람들은 눈꺼풀 안쪽에 다음과 같은 보안 지침을 문신해야합니다.
절대 일반 텍스트 암호를 메모리에 보관하지 마십시오.

WPF / Silverlight PasswordBoxPassword속성에 대해 DP를 노출시키지 않는 이유 는 보안 관련입니다.
WPF / Silverlight가 DP Password를 보관해야한다면 암호 자체를 메모리에 암호화되지 않은 상태로 유지해야하는 프레임 워크가 필요합니다. 꽤 번거로운 보안 공격 경로로 간주됩니다. PasswordBox(종류의)를 사용하여 암호화 된 메모리와 비밀번호를 액세스 할 수있는 유일한 방법은 CLR 속성을 통해입니다.

PasswordBox.PasswordCLR 속성에 액세스 할 때 CLR 속성을 변수 또는 속성 값으로 배치하지 않는 것이 좋습니다 .
클라이언트 시스템 RAM에 암호를 일반 텍스트로 유지하는 것은 보안 상 필요하지 않습니다.
그래서 그것을 제거하십시오public string Password { get; set; } 당신이 거기 .

에 접근 할 때 PasswordBox.Password그냥 꺼내서 서버로 최대한 빨리 배송하십시오. 암호 값을 유지하지 말고 다른 클라이언트 시스템 텍스트처럼 취급하지 마십시오. 메모리에 일반 텍스트 비밀번호를 유지하지 마십시오.

나는 이것이 MVVM 패턴을 깨뜨린다는 것을 알고 있지만, 당신은 PasswordBox.PasswordAttached DP에 바인딩 하거나, ViewModel 또는 다른 유사한 shenanigans에 암호를 저장 해서는 안됩니다 .

지나치게 아키텍쳐 된 솔루션을 찾고 있다면 다음과 같습니다.
1. IHavePassword암호를 입력하지 않은 텍스트를 반환하는 한 가지 방법으로 인터페이스를 만듭니다 .
2. 인터페이스를 UserControl구현하십시오 IHavePassword.
3. 인터페이스 UserControl를 구현할 때 IoC에 인스턴스를 등록하십시오 IHavePassword.
4. 암호를 요구하는 서버 요청이 발생하면 IHavePassword구현을 위해 IoC에 전화를 걸어 많은 호의적 인 암호를 얻으십시오.

그냥 가져 가라

-저스틴


답변

내 2 센트 :

WPF와 MVVM을 사용하여 일반적인 로그인 대화 상자 (사용자 및 암호 상자와 “확인”단추)를 한 번 개발했습니다. PasswordBox 컨트롤 자체를 “확인”버튼에 연결된 명령에 매개 변수로 전달하여 암호 바인딩 문제를 해결했습니다. 그래서보기에 :

<PasswordBox Name="txtPassword" VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding Path=OkCommand}"
   CommandParameter="{Binding ElementName=txtPassword}"/>

그리고 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
}

이제 ViewModel이 View 구현 방법에 대해 알고 있기 때문에 MVVM 패턴을 약간 위반하지만 해당 프로젝트에서는 감당할 수 있습니다. 누군가에게도 유용하기를 바랍니다.


답변

어쩌면 나는 뭔가를 놓치고 있지만 대부분의 솔루션은 너무 복잡하고 안전한 관행을 없애는 것처럼 보입니다.

이 방법은 MVVM 패턴을 위반하지 않으며 완전한 보안을 유지합니다. 예, 기술적으로 코드 뒤에 있지만 “특별한 경우”바인딩에 지나지 않습니다. ViewModel에는 여전히 View 구현에 대한 지식이 없으므로 PasswordBox를 ViewModel에 전달하려고하면 내 마음에 있습니다.

Code Behind! = 자동 MVVM 위반. 그것은 모두 당신이 무엇을하는지에 달려 있습니다. 이 경우 바인딩을 수동으로 코딩하기 때문에 UI 구현의 일부로 간주되므로 괜찮습니다.

ViewModel에서 간단한 속성입니다. 어떤 이유로 든 ViewModel 외부에서 검색 할 필요가 없기 때문에 “쓰기 전용”으로 만들었지 만 반드시 그럴 필요는 없습니다. 문자열이 아니라 SecureString입니다.

public SecureString SecurePassword { private get; set; }

xaml에서 PasswordChanged 이벤트 핸들러를 설정합니다.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

코드 뒤에 :

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).SecurePassword = ((PasswordBox)sender).SecurePassword; }
}

이 방법을 사용하면 비밀번호는 항상 SecureString에 유지되므로 최대한의 보안을 제공합니다. 보안에 신경 쓰지 않거나 다운 스트림 메서드에 필요한 일반 텍스트 암호가 필요한 경우 (참고 : 암호가 필요한 대부분의 .NET 메서드도 SecureString 옵션을 지원하므로 실제 텍스트 암호가 필요하지 않을 수 있음) 당신이 생각하는 경우에도) 대신 Password 속성을 사용할 수 있습니다. 이처럼 :

(ViewModel 속성)

public string Password { private get; set; }

(코드 뒤)

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((dynamic)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

강력한 타이핑을 유지하려는 경우 (동적) 캐스트를 ViewModel 인터페이스로 대체 할 수 있습니다. 그러나 실제로 “정상적인”데이터 바인딩은 강력하게 입력되지 않으므로 그렇게 큰 문제는 아닙니다.

private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
{
    if (this.DataContext != null)
    { ((IMyViewModel)this.DataContext).Password = ((PasswordBox)sender).Password; }
}

모든 세계에서 가장 좋은 점-암호는 안전하고 ViewModel에는 다른 속성과 같은 속성이 있으며 View는 외부 참조가 필요없는 자체 포함되어 있습니다.


답변

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

그리고이 명령은 메소드를 실행합니다 :

private void ExecutePasswordChangedCommand(PasswordBox obj)
{
   if (obj != null)
     Password = obj.Password;
}


답변

이것은 나를 위해 잘 작동합니다.

<Button Command="{Binding Connect}"
        CommandParameter="{Binding ElementName=MyPasswordBox}"/>


답변

MVVM 패턴을 위반하지 않는 간단한 해결책은 암호를 수집하는 ViewModel에 이벤트 (또는 위임)를 도입하는 것입니다.

에서 의 ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

이 EventArgs와 함께 :

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

에서 보기 , 암호 값의 뷰 모델 및 채우기 만들기에 대한 이벤트에 가입.

_viewModel.HarvestPassword += (sender, args) =>
    args.Password = passwordBox1.Password;

에서 의 ViewModel 암호를 필요로 할 때, 당신은 이벤트가 발생하고 거기에서 암호를 수확 할 수 있습니다 :

if (HarvestPassword == null)
  //bah 
  return;

var pwargs = new HarvestPasswordEventArgs();
HarvestPassword(this, pwargs);

LoginHelpers.Login(Username, pwargs.Password);


답변

다양한 솔루션을 살펴 보는 데 많은 시간을 보냈습니다. 나는 데코레이터 아이디어를 좋아하지 않았고, 행동은 유효성 검사 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;
        }
    }
}

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

그리고 XAML 사용법 :

<PasswordBox controls:PasswordBoxHelper.SecurePassword="{Binding LogonPassword, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">

뷰 모델의 내 속성은 다음과 같습니다.

[RequiredSecureString]
public SecureString LogonPassword
{
   get
   {
       return _logonPassword;
   }
   set
   {
       _logonPassword = value;
       NotifyPropertyChanged(nameof(LogonPassword));
   }
}

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

여기 있습니다. 완벽하고 테스트 된 순수한 MVVM 솔루션.