IT story

MVVM에서 PasswordBox에 바인딩하는 방법

hot-time 2020. 4. 10. 08:20
반응형

MVVM에서 PasswordBox에 바인딩하는 방법


PasswordBox에 바인딩하는 데 문제가 있습니다. 보안 위험이있는 것 같지만 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 PasswordBox가 암호 속성에 대해 DP를 노출시키지 않는 이유는 보안 관련입니다. WPF / Silverlight가 DP for Password를 유지하려면 프레임 워크에서 암호 자체를 메모리에 암호화되지 않은 상태로 유지해야합니다. 이는 상당히 까다로운 보안 공격 경로로 간주됩니다.

PasswordBox(종류의)를 사용하여 암호화 된 메모리와 비밀번호를 액세스 할 수있는 유일한 방법은 CLR 속성을 통해입니다.

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

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

나는 이것이 MVVM 패턴을 깨뜨린다는 것을 알고 있지만, PasswordBox.Password연결된 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);

바인딩 가능한 암호 상자 인 GIST를 여기에 게시했습니다 .

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

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.");
    }
}

뷰 모델에서 :

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

뷰 모델은를 사용하기 때문에 nor에 IWrappedParameter<T>대한 지식이 없어도 됩니다. 이 방법으로 뷰 모델에서 객체를 분리 하고 MVVM 패턴을 중단 할 수 없습니다.PasswordBoxWrapperPasswordBoxConverterPasswordBox

보기에서 :

<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}}" />

이 구현은 약간 다릅니다. ViewModel에서 속성의 View thru 바인딩에 암호 상자를 전달하면 명령 매개 변수가 사용되지 않습니다. ViewModel은 뷰를 무시합니다. SkyDrive에서 다운로드 할 수있는 VB vs 2010 프로젝트가 있습니다. 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

속성의 자체 초기화를 수행하기 위해 백업 필드를 사용합니다.

그런 다음 Xaml에서 ContentControl 또는 컨트롤 컨테이너의 내용을 바인딩합니다. 예 :

 <ContentControl Grid.Column="1" Grid.Row="1" Height="23" Width="120" Content="{Binding Path=ThePassWordBox}" HorizontalAlignment="Center" VerticalAlignment="Center" />

거기에서 암호 상자를 완전히 제어 할 수 있습니다. 또한 PasswordAccessor (문자열 기능)를 사용하여 로그인 할 때 또는 암호를 원할 때 암호 값을 반환합니다. 이 예에서는 일반 사용자 개체 모델에 공용 속성이 있습니다. 예:

Public Property PasswordAccessor() As Func(Of String)

사용자 개체에서 암호 문자열 속성은 백업 저장소없이 읽기 전용이므로 PasswordBox에서 암호 만 반환합니다. 예:

Public ReadOnly Property PassWord As String
    Get
        Return If((PasswordAccessor Is Nothing), String.Empty, PasswordAccessor.Invoke())
    End Get
End Property

그런 다음 ViewModel에서 접근자를 만들고 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

로그인을 위해 비밀번호 문자열이 필요할 때 실제로 함수를 호출하여 비밀번호를 가져 와서 반환하는 User Objects Password 속성을 얻으면 실제 비밀번호는 사용자 객체에 저장되지 않습니다. 예 : 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

그렇게해야합니다. ViewModel에는 View 컨트롤에 대한 지식이 필요하지 않습니다. 뷰는 이미지 나 다른 리소스에 대한 뷰 바인딩과 다르지 않고 ViewModel의 속성에 바인딩됩니다. 이 경우 resource (Property)는 usercontrol이됩니다. ViewModel이 속성을 생성 및 소유하고 속성이 View와 독립적이므로 테스트가 가능합니다. 보안에 관해서는이 구현이 얼마나 좋은지 모르겠습니다. 그러나 함수를 사용하여 값은 속성에서 액세스 한 속성 자체에 저장되지 않습니다.


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


암호를 어디에나 저장하지 않는 것이 중요하다는 데 동의하지만 뷰없이 뷰 모델을 인스턴스화하고 이에 대한 테스트를 실행할 수 있어야합니다.

나를 위해 일한 해결책은 PasswordBox.Password 함수를 뷰 모델에 등록하고 로그인 코드를 실행할 때 뷰 모델에서 호출하는 것입니다.

수행 뷰의 코드 숨김 코드의 라인을 의미한다.

그래서 내 Login.xaml에는

<PasswordBox x:Name="PasswordBox"/>

그리고 Login.xaml.cs에는

LoginViewModel.PasswordHandler = () => PasswordBox.Password;

그런 다음 LoginViewModel.cs에 PasswordHandler가 정의되어 있습니다.

public Func<string> PasswordHandler { get; set; }

로그인이 필요할 때 코드는 핸들러를 호출하여 뷰에서 비밀번호를 가져옵니다.

bool loginResult = Login(Username, PasswordHandler());

이렇게하면 뷰 모델을 테스트 할 때 PasswordHandler를 익명 메소드로 설정하여 테스트에 사용하려는 비밀번호를 전달할 수 있습니다.


나는이 방법을 사용하고 암호 상자를 전달했지만 MVVM을 위반하지만 복잡한 쉘 환경 인 쉘 내 로그인을 위해 데이터 템플릿이있는 컨텐츠 컨트롤을 사용했기 때문에 필수적이었습니다. 따라서 쉘 뒤의 코드에 액세스하는 것은 쓰레기 일 것입니다.

암호 상자를 전달하는 것은 내가 아는 한 코드 뒤에서 제어에 액세스하는 것과 같습니다. 암호에 동의하고 메모리에 보관하지 마십시오.이 구현에서는보기 모델에 암호에 대한 속성이 없습니다.

버튼 명령

Command="{Binding Path=DataContext.LoginCommand, ElementName=MyShell}" CommandParameter="{Binding ElementName=PasswordBox}"

뷰 모델

private void Login(object parameter)
{
    System.Windows.Controls.PasswordBox p = (System.Windows.Controls.PasswordBox)parameter;
    MessageBox.Show(p.Password);
}

나는 이것이 일반적인 문제이기 때문에 솔루션에 믹스를 던질 것이라고 생각했습니다 ... 그리고 많은 옵션을 갖는 것이 항상 좋은 것입니다.

나는 단순히 포장 PasswordBoxA의 UserControl및 구현 DependencyProperty결합 할 수 있도록. 나는 명확한 텍스트를 메모리에 저장하지 않기 위해 할 수있는 모든 것을하고 있으므로 모든 것은 a SecureStringPasswordBox.Password속성을 통해 수행됩니다 . foreach루프 중에 각 문자가 노출되지만 매우 간단합니다. 솔직히이 짧은 노출로 인해 WPF 응용 프로그램이 손상 될 염려가 있다면 처리해야 할 더 큰 보안 문제가 있습니다.

이것 UserControl의 장점 은 MVVM 규칙, 심지어 "순수한"규칙을 위반하지 않는다는 것입니다. 왜냐하면 이것은 왜냐하면 코드 비하인드를 가질 수 있기 때문입니다. 당신이 그것을 사용하는 경우,이 사이의 순수한 통신 수 ViewViewModel당신이하지 VideModel의 어떤 부분을 알고있는 View나 암호의 소스를. 에 바인딩되어 있는지 확인 SecureString하십시오 ViewModel.

BindablePasswordBox.xaml

<UserControl x:Class="BK.WPF.CustomControls.BindanblePasswordBox"
             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="22" d:DesignWidth="150">
    <PasswordBox x:Name="PswdBox"/>
</UserControl>

BindablePasswordBox.xaml.cs (버전 1-양방향 바인딩 지원 없음)

using System.ComponentModel;
using System.Security;
using System.Windows;
using System.Windows.Controls;

namespace BK.WPF.CustomControls
{
    public partial class BindanblePasswordBox : UserControl
    {
        public static readonly DependencyProperty PasswordProperty =
            DependencyProperty.Register("Password", typeof(SecureString), typeof(BindanblePasswordBox));

        public SecureString Password
        {
            get { return (SecureString)GetValue(PasswordProperty); }
            set { SetValue(PasswordProperty, value); }
        }

        public BindanblePasswordBox()
        {
            InitializeComponent();
            PswdBox.PasswordChanged += PswdBox_PasswordChanged;
        }

        private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
        {
            var secure = new SecureString();
            foreach (var c in PswdBox.Password)
            {
                secure.AppendChar(c);
            }
            Password = secure;
        }
    }
}

버전 1의 사용법 :

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=OneWayToSource}"/>

BindablePasswordBox.xaml.cs (버전 2-양방향 바인딩 지원)

public partial class BindablePasswordBox : UserControl
{
    public static readonly DependencyProperty PasswordProperty =
        DependencyProperty.Register("Password", typeof(SecureString), typeof(BindablePasswordBox),
        new PropertyMetadata(PasswordChanged));

    public SecureString Password
    {
        get { return (SecureString)GetValue(PasswordProperty); }
        set { SetValue(PasswordProperty, value); }
    }

    public BindablePasswordBox()
    {
        InitializeComponent();
        PswdBox.PasswordChanged += PswdBox_PasswordChanged;
    }

    private void PswdBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
        var secure = new SecureString();
        foreach (var c in PswdBox.Password)
        {
            secure.AppendChar(c);
        }
        if (Password != secure)
        {
            Password = secure;
        }
    }

    private static void PasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var pswdBox = d as BindablePasswordBox;
        if (pswdBox != null && e.NewValue != e.OldValue)
        {
            var newValue = e.NewValue as SecureString;
            if (newValue == null)
            {
                return;
            }

            var unmanagedString = IntPtr.Zero;
            string newString;
            try
            {
                unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(newValue);
                newString = Marshal.PtrToStringUni(unmanagedString);
            }
            finally
            {
                Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
            }

            var currentValue = pswdBox.PswdBox.Password;
            if (currentValue != newString)
            {
                pswdBox.PswdBox.Password = newString;
            }
        }
    }
}

버전 2의 사용법 :

<local:BindanblePasswordBox Width="150" HorizontalAlignment="Center"
                            VerticalAlignment="Center"
                            Password="{Binding Password, Mode=TwoWay}"/>

당신은 .. 그것을보고, 연결된 속성과 그것을 할 수 MVVM으로 PasswordBox의를


나와 같은 완전한 초보자를 위해 Konamiman위의 제안에 대한 완전한 작업 샘플이 있습니다. 감사합니다 Konamiman.

XAML

    <PasswordBox x:Name="textBoxPassword"/>
    <Button x:Name="buttonLogin" Content="Login"
            Command="{Binding PasswordCommand}"
            CommandParameter="{Binding ElementName=textBoxPassword}"/> 

뷰 모델

public class YourViewModel : ViewModelBase
{
    private ICommand _passwordCommand;
    public ICommand PasswordCommand
    {
        get {
            if (_passwordCommand == null) {
                _passwordCommand = new RelayCommand<object>(PasswordClick);
            }
            return _passwordCommand;
        }
    }

    public YourViewModel()
    {
    }

    private void PasswordClick(object p)
    {
        var password = p as PasswordBox;
        Console.WriteLine("Password is: {0}", password.Password);
    }
}

보시다시피 암호에 바인딩하고 있지만 정적 클래스에 바인딩 할 수 있습니다.

그것은이다 연결된 속성 . 이러한 종류의 속성 DependencyObject은 선언 된 유형뿐만 아니라 모든 종류의에 적용 할 수 있습니다 . 따라서 PasswordHelper정적 클래스 에서 선언되었지만 사용하는 클래스에 적용됩니다 PasswordBox.

이 연결된 속성을 사용하려면 PasswordViewModel 속성에 바인딩하면됩니다 .

<PasswordBox w:PasswordHelper.Attach="True" 
         w:PasswordHelper.Password="{Binding Password}"/>

나는 다음과 같이했다 :

XAML :

<PasswordBox x:Name="NewPassword" PasswordChanged="NewPassword_PasswordChanged"/>
<!--change tablenameViewSource: yours!-->
<Grid DataContext="{StaticResource tablenameViewSource}" Visibility="Hidden">
        <TextBox x:Name="Password" Text="{Binding password, Mode=TwoWay}"/>
</Grid>

씨#:

private void NewPassword_PasswordChanged(object sender, RoutedEventArgs e)
    {
        try
        {
           //change tablenameDataTable: yours! and tablenameViewSource: yours!
           tablenameDataTable.Rows[tablenameViewSource.View.CurrentPosition]["password"] = NewPassword.Password;
        }
        catch
        {
            this.Password.Text = this.NewPassword.Password;
        }
    }

그것은 나를 위해 작동합니다!


앞에서 언급했듯이 VM은 View를 인식하지 못하지만 PasswordBox 전체를 전달하는 것이 가장 간단한 방법처럼 보입니다. 따라서 전달 된 매개 변수를 PasswordBox로 캐스팅하는 대신 Reflection을 사용하여 암호 속성을 추출하십시오. 이 경우 VM은 속성 속성이있는 일종의 암호 컨테이너를 예상합니다 (MVMM Light-Toolkit에서 RelayCommands를 사용하고 있습니다).

public RelayCommand<object> SignIn
{
    get
    {
        if (this.signIn == null)
        {
            this.signIn = new RelayCommand<object>((passwordContainer) => 
                {
                    var password = passwordContainer.GetType().GetProperty("Password").GetValue(passwordContainer) as string;
                    this.authenticationService.Authenticate(this.Login, password);
                });
        }

        return this.signIn;
    }
}

익명 클래스로 쉽게 테스트 할 수 있습니다.

var passwordContainer = new
    {
        Password = "password"
    };

나 에게이 두 가지 모두 잘못 느낀다.

  • 일반 텍스트 비밀번호 속성 구현
  • 보내기 PasswordBox뷰 모델에 명령 매개 변수로

CO에서 Steve가 설명한대로 SecurePassword (SecureString 인스턴스)를 전송하는 것은 허용되는 것 같습니다. Behaviors코드 숨김을 선호 하고 뷰 모델에서 비밀번호를 재설정 할 수 있어야한다는 추가 요구 사항도있었습니다.

Xaml ( PasswordViewModel 속성 임) :

<PasswordBox>
    <i:Interaction.Behaviors>
        <behaviors:PasswordBinding BoundPassword="{Binding Password, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
</PasswordBox>

행동:

using System.Security;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;

namespace Evidence.OutlookIntegration.AddinLogic.Behaviors
{
    /// <summary>
    /// Intermediate class that handles password box binding (which is not possible directly).
    /// </summary>
    public class PasswordBoxBindingBehavior : Behavior<PasswordBox>
    {
        // BoundPassword
        public SecureString BoundPassword { get { return (SecureString)GetValue(BoundPasswordProperty); } set { SetValue(BoundPasswordProperty, value); } }
        public static readonly DependencyProperty BoundPasswordProperty = DependencyProperty.Register("BoundPassword", typeof(SecureString), typeof(PasswordBoxBindingBehavior), new FrameworkPropertyMetadata(OnBoundPasswordChanged));

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

        /// <summary>
        /// Link up the intermediate SecureString (BoundPassword) to the UI instance
        /// </summary>
        private void AssociatedObjectOnPasswordChanged(object s, RoutedEventArgs e)
        {
            this.BoundPassword = this.AssociatedObject.SecurePassword;
        }

        /// <summary>
        /// Reacts to password reset on viewmodel (ViewModel.Password = new SecureString())
        /// </summary>
        private static void OnBoundPasswordChanged(object s, DependencyPropertyChangedEventArgs e)
        {
            var box = ((PasswordBoxBindingBehavior)s).AssociatedObject;
            if (box != null)
            {
                if (((SecureString)e.NewValue).Length == 0)
                    box.Password = string.Empty;
            }
        }

    }
}

Windows 범용 앱에서

이 코드를 "Password"속성과 함께 사용하고 modelView와 바인딩 할 수 있습니다

 <PasswordBox x:Uid="PasswordBox" Password="{Binding Waiter.Password, Mode=TwoWay}" Name="txtPassword" HorizontalAlignment="Stretch" Margin="50,200,50,0" VerticalAlignment="Top"/>


매우 간단합니다. 암호에 대한 다른 속성을 만들고 TextBox와 바인딩

그러나 모든 입력 작업은 실제 비밀번호 속성으로 수행됩니다.

개인 문자열 _Password;

    public string PasswordChar
    {
        get
        {
            string szChar = "";

            foreach(char szCahr in _Password)
            {
                szChar = szChar + "*";
            }

            return szChar;
        }

        set
        {
            _PasswordChar = value; NotifyPropertyChanged();
        }
    }

공개 문자열 비밀번호 {get {return _Password; }

        set
        {
            _Password = value; NotifyPropertyChanged();
            PasswordChar = _Password;
        }
    }


이 구현으로 인한 위험을 알고있는 사람은 ViewModel과 비밀번호를 동기화하기 위해 Mode = OneWayToSource 를 추가하면 됩니다 .

XAML

<PasswordBox
    ff:PasswordHelper.Attach="True"
    ff:PasswordHelper.Password="{Binding Path=Password, Mode=OneWayToSource}" />

WPF 응용 프로그램 프레임 워크 (WAF) 프로젝트 의 ViewModel 샘플 응용 프로그램에서 PasswordBox에 대한 솔루션을 찾을 수 있습니다.

그러나 저스틴이 옳습니다. View와 ViewModel 사이에 암호를 일반 텍스트로 전달하지 마십시오. 대신 SecureString을 사용하십시오 (MSDN PasswordBox 참조).


인증 검사 다음에 중재자 클래스가 뷰에 대해 호출 한 하위 (인증 검사를 구현 함)를 사용하여 암호를 데이터 클래스에 기록했습니다.

완벽한 솔루션은 아닙니다. 그러나 암호를 옮길 수 없다는 문제를 해결했습니다.


아직 언급되지 않은 간결한 MVVM 친화적 솔루션을 사용하고 있습니다. 먼저 XAML에서 PasswordBox의 이름을 지정합니다.

<PasswordBox x:Name="Password" />

그런 다음 단일 메소드 호출을 뷰 생성자에 추가합니다.

public LoginWindow()
{
    InitializeComponent();
    ExposeControl<LoginViewModel>.Expose(this, view => view.Password,
        (model, box) => model.SetPasswordBox(box));
}

그리고 그게 다야. 뷰 모델은 DataContext를 통해 뷰에 연결될 때 알림을 받고 분리 될 때 다른 알림을받습니다. 이 알림의 내용은 람다를 통해 구성 할 수 있지만 일반적으로 뷰 모델에서 setter 또는 메서드 호출로 문제가있는 컨트롤을 매개 변수로 전달합니다.

뷰를 자식 컨트롤 대신 인터페이스에 노출시켜 MVVM 친화적으로 만들 수 있습니다.

위의 코드 는 내 블로그에 게시 된 도우미 클래스를 사용 합니다.


나는 이것을 작동시키기 위해 여러 해를 보냈다. 결국, 나는 포기하고 DevExpress의 PasswordBoxEdit을 사용했습니다.

그것은 끔찍한 트릭을 당기지 않고 바인딩 할 수 있기 때문에 가장 간단한 솔루션입니다.

DevExpress 웹 사이트의 솔루션

기록을 위해, 나는 어떤 식 으로든 DevExpress와 제휴하지 않습니다.


<UserControl x:Class="Elections.Server.Handler.Views.LoginView"
             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"
             xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
             xmlns:cal="http://www.caliburnproject.org"
             mc:Ignorable="d" 
             Height="531" Width="1096">
    <ContentControl>
        <ContentControl.Background>
            <ImageBrush/>
        </ContentControl.Background>
        <Grid >
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,100,0,0" VerticalAlignment="Top" Width="160">
                <TextBox TextWrapping="Wrap"/>
            </Border>
            <Border BorderBrush="#FFABADB3" BorderThickness="1" HorizontalAlignment="Left" Height="23" Margin="900,150,0,0" VerticalAlignment="Top" Width="160">
                <PasswordBox x:Name="PasswordBox"/>
            </Border>
            <Button Content="Login" HorizontalAlignment="Left" Margin="985,200,0,0" VerticalAlignment="Top" Width="75">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cal:ActionMessage MethodName="Login">
                            <cal:Parameter Value="{Binding ElementName=PasswordBox}" />
                        </cal:ActionMessage>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

        </Grid>
    </ContentControl>
</UserControl>

using System;
using System.Windows;
using System.Windows.Controls;
using Caliburn.Micro;

namespace Elections.Server.Handler.ViewModels
{
    public class LoginViewModel : PropertyChangedBase
    {
        MainViewModel _mainViewModel;
        public void SetMain(MainViewModel mainViewModel)
        {
            _mainViewModel = mainViewModel;
        }

        public void Login(Object password)
        {
            var pass = (PasswordBox) password;
            MessageBox.Show(pass.Password);

            //_mainViewModel.ScreenView = _mainViewModel.ControlPanelView;
            //_mainViewModel.TitleWindow = "Panel de Control";
            //HandlerBootstrapper.Title(_mainViewModel.TitleWindow);
        }
    }
}

;) 쉬워요!


음 내 대답은 MVVM 패턴에서 더 간단합니다.

수업 모델에서

public string password;

PasswordChangedCommand = new DelegateCommand<RoutedEventArgs>(PasswordChanged);

Private void PasswordChanged(RoutedEventArgs obj)

{

    var e = (WatermarkPasswordBox)obj.OriginalSource;

    //or depending or what are you using

    var e = (PasswordBox)obj.OriginalSource;

    password =e.Password;

}

win에서 제공하는 PasswordBox 또는 XCeedtoolkit에서 제공하는 WatermarkPasswordBox의 password 속성은 RoutedEventArgs를 생성하여 바인딩 할 수 있습니다.

지금 xmal보기에서

<Xceed:WatermarkPasswordBox Watermark="Input your Password" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </Xceed:WatermarkPasswordBox>

또는

<PasswordBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" PasswordChar="*" >

        <i:Interaction.Triggers>

            <i:EventTrigger EventName="PasswordChanged">

                <prism:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path= DataContext.PasswordChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path= Password}"/>

            </i:EventTrigger>

        </i:Interaction.Triggers>

    </PasswordBox>

하나의 컨트롤과 하나의 명령으로 모두 결합하려면

<PasswordBox Name="PasswordBoxPin" PasswordChar="*">
    <PasswordBox.InputBindings>
        <KeyBinding Key="Return" Command="{Binding AuthentifyEmpCommand}" CommandParameter="{Binding ElementName=PasswordBoxPin}"/>
    </PasswordBox.InputBindings>
</PasswordBox>

그리고 Vm에서 (Konamiman이 보여준 것처럼)

public void AuthentifyEmp(object obj)
{
    var passwordBox = obj as PasswordBox;
    var password = passwordBox.Password;
}
private RelayCommand _authentifyEmpCommand;
public RelayCommand AuthentifyEmpCommand => _authentifyEmpCommand ?? (_authentifyEmpCommand = new RelayCommand(AuthentifyEmp, null));

여기에 내가 가져 가라.

  1. 첨부 된 특성을 사용하여 비밀번호를 바인드하면 비밀번호 보안 목적이 무효화됩니다. 비밀번호 상자의 비밀번호 특성은 이유로 바인딩 할 수 없습니다.

  2. 암호 상자를 명령 매개 변수로 전달하면 ViewModel이 제어를 인식하게됩니다. ViewModel을 재사용 가능한 크로스 플랫폼으로 만들려는 경우에는 작동하지 않습니다. VM에 View 또는 다른 컨트롤을 알리지 마십시오.

  3. 암호를 제공하는 간단한 작업을 수행하려면 새 속성, 인터페이스, 암호 변경 이벤트 구독 또는 기타 복잡한 사항을 도입해야한다고 생각하지 않습니다.

XAML

<PasswordBox x:Name="pbPassword" />
<Button Content="Login" Command="{Binding LoginCommand}" x:Name="btnLogin"/>

코드 숨김-코드 숨김을 사용한다고해서 반드시 MVVM을 위반하는 것은 아닙니다. 비즈니스 로직을 넣지 않는 한.

btnLogin.CommandParameter = new Func<string>(()=>pbPassword.Password); 

뷰 모델

LoginCommand = new RelayCommand<Func<string>>(getpwd=> { service.Login(username, getpwd()); });

참고 URL : https://stackoverflow.com/questions/1483892/how-to-bind-to-a-passwordbox-in-mvvm

반응형