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


251

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.


3
글쎄 그것은 코드가 작동하지 않는 것으로 판명되었지만 여기서 대체 코드를 시도했지만 완벽하게 작동합니다. blog.functionalfun.net/2008/06/…
mark smith

5
전체 비밀번호 상자 컨트롤을 전달하지 않으면 뷰 모델과 뷰가 분리되지 않습니까?

답변:


164

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

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

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에 전화를 걸어 많은 호의적 인 암호를 얻으십시오.

그냥 가져 가라

-저스틴


19
WPF 용 VM에서 SecureString을 사용하여이 문제를 해결할 수 없습니까? Silverlight에 대한 내용이없는 것 같습니다.
Bryant

35
나는 당신의 의도와 당신이 전달하는 메시지에 동의하지만 당신의 대답은 당신 이이 접근법을 따르면 암호 문자열이 절대 메모리에 없다는 것을 암시합니다. 암호 값은 사용자가 입력 한 순간부터 메모리에 저장됩니다. 암호 문구가 포함 된 속성을 제거하는 것은 좋은 생각이며 가비지 수집기를 위해 남겨둔 암호 복사본을 제한하거나 프로그램의 일부로 실행되는 다른 관리 및 관리되지 않는 코드에 의해 발견 될 수 있습니다. 그것을 숨기지 마십시오.
IanNorton

182
대부분의 경우 보안 수준이 필요 하지 않습니다 . 암호를 훔치는 다른 방법이 너무 많을 때 한 가지 어려운 점은 무엇입니까? Atleast WPF는 @Bryant와 같은 SecureString의 사용을 허용해야한다고 말했습니다.
chakrit

335
악의적 인 사용자가 컴퓨터의 RAM에 액세스 할 수있는 경우 암호를 도용하는 것보다 더 큰 문제가 있습니다.
Cameron MacFarland

13
몇 년 동안 PasswordBox와 같은 방식으로 동작하지만 텍스트 값만 SecureString으로 반환하는 사용자 지정 사용자 정의 컨트롤을 사용해 왔습니다. 예, 이렇게하면 Snoop이 암호를 일반 텍스트로 표시하지 않습니다. 그러나 SecureString의 일반 텍스트 값을 여전히 쉽게 추출 할 수 있으며 초보자 해킹 만 막을 수 있습니다. 시스템이 Snoop과 같은 주요 로거 및 스니퍼를 비밀리에 사용하는 위험이있는 경우 시스템 보안을 다시 평가해야합니다.
Mike Christian

199

내 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 패턴을 약간 위반하지만 해당 프로젝트에서는 감당할 수 있습니다. 누군가에게도 유용하기를 바랍니다.


안녕하세요 Konamiman, Execute 메서드가 호출 될 때 내 뷰 모델에는 클래스 User (login, pass) 및 명령 인증이 있습니다. 그 컨텍스트에서 Execute를 어떻게 사용할 수 있습니까?

3
매우 도움이되었습니다. 감사합니다. 참고로 누군가 _loginCommand = new RelayCommand (param => Login (UserName, (PasswordBox) param), param => CanLogIn)와 같은 것을 보는 데 익숙 할 것입니다.
척 로랑스

5
이것은 괜찮은 해결책이지만 암호 + 암호 확인 콤보와 같은 것에 실패합니다
Julien

안녕하세요 Konamiman, 솔루션을 사용하고 있지만 Windows 8.1 Store 앱에서는 작동하지 않습니다. 이 질문을했습니다 : stackoverflow.com/questions/26221594/…
VansFannel

2
감사합니다! 이것은 데이터를 UI 스레드에서 기본 프로그램 스레드로 이동하는 데있어 큰 문제를 해결했습니다. SecureString 접근 방식을 구현하고 ~ 가능한 빨리 비밀번호를 제거하십시오 ~. 버려. 폐기하십시오. 그것을 지우십시오. 해야 할 일을하십시오. 또한 IDisposable을 구현해야합니다.
Steven C. Britton

184

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

이 방법은 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는 외부 참조가 필요없는 자체 포함되어 있습니다.


1
이것은 나에게 좋아 보인다! 보안 측면에서 매우 엄격하고 싶었다면 이것이 확실하지 않을지 모르지만 나에게는 완벽한 중간 지점입니다. 감사!
jrich523

3
MVVM 및 편집증에 대한 엄격한 교리에 대한 실용성에 감사드립니다. 고맙습니다.
Bruce Pierson


1
참으로 좋습니다. MS가 방금 SecureString 유형의 비밀번호 DP를이 컨트롤에 추가했으면합니다.
Keith Hill

1
보안과 MVVM을 유지하므로 완벽한 답변입니다.
LoRdPMN

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>

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

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

3
참고xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX

PasswordBox의 이름을 지정하지 않아도됩니다 CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(참고 : not RelativeSource Self ).
wondra

이 솔루션은 MVVM 패턴을 위반합니다.
BionicCode

13

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

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

3
CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}는 어떻습니까?
LukeN

2
LukeN, 이것은 작동하지 않습니다 (적어도 나를 위해). 아마도 같은 이유로-SecurePassword는 종속성 속성이 아닙니다.
vkrzv

ICommand이 뷰 모델에서 구현 되었다고 가정하면 이 솔루션은 MVVM 패턴을 위반합니다.
BionicCode

9

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

누락 된 한 가지는 뷰 모델 이벤트에 뷰를 구독 할 때 WeakEventManager<TEventSource, TEventArgs>메모리 누수를 피하기 위해 a 를 사용해야한다는 것 입니다. 종종 뷰는 뷰 모델과 동일한 수명을 갖지 않습니다. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel

이 솔루션은 단순하고 MVVM을 위반하지 않으며 최소한의 코드 만 사용하며 passwordbox를 올바르게 사용할 수 있습니다 ( 'SecurePassword'를 대신 사용하는 경우). 또한 스마트 카드와 같은 다른 HarvestPassword 메소드를 구현하는 것도 간단합니다.
Matt

8

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


7

여기 에 바인딩 가능한 암호 상자 인 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;
        }
    }
}

1
이것이 나쁘지는 않지만 패딩 및 tabindex와 같은 간단한 속성을 설정하는 기능을 잃게됩니다.
Julien

1
테일러, 요점을 답변에 사용할 수 있도록 요점을 인라인했습니다. (그렇지 않으면 링크 전용 답변처럼 보였습니다. 그냥 삭제되지 않도록 노력하십시오.) 인라인 된 내용을 자유롭게 엉망으로 만드십시오.
Lynn Crumbling

@Julien하지만 스타일로 해결할 수 있습니다. 나는이 문제를 비슷한 방식으로 해결하지만 ContentControlXAML의 내용과 스타일로 PasswordBox를 사용할 수 있습니다. 의 목적은 이벤트 ContentControl를 구독하고 PasswordChanged양방향 바인딩 가능 속성을 노출하는 것입니다. 대체로 65 줄의 코드와이 클래스가 꾸미는 것과 거의 비슷합니다. 다음의 내 요점 여기를 참조하십시오 gist.github.com/leidegre/c7343b8c720000fe3132
존 Leidegren에게

6

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

로그인을 위해 Password 문자열 say가 필요할 때 실제로 Function을 호출하여 암호를 가져 와서 반환하는 User Objects Password 속성을 얻으면 실제 암호는 User Object에 의해 저장되지 않습니다. 예 : 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와 독립적이므로 테스트가 가능합니다. 보안에 관해서는이 구현이 얼마나 좋은지 모르겠습니다. 그러나 함수를 사용하여 값은 속성에서 액세스 한 속성 자체에 저장되지 않습니다.


6

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

매우 우아한 솔루션 imo. 나는 이것을 기반으로했습니다. 유일한 차이점 : 나는 StringString 대신 SecureString SecurePassword를 로그인 기능에 전달합니다. 암호를 사용하여 메모리 주위를 비행하는 암호화되지 않은 문자열이 없습니다.
저를 당근이라고 부르십시오

오래되었지만 내 RelayCommand로 인해 작동하지 않는 것 같습니다. 당신을 추가해도 될까요?
Ecnerwal

5

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

나를 위해 일한 해결책은 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를 익명 메소드로 설정하여 테스트에 사용하려는 비밀번호를 전달할 수 있습니다.


4

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

나는 단순히 포장 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}"/>

나는 이것을 구현하려고 시도했지만 UI에서 암호를 업데이트하면 무한 루프가 발생합니다. if (Password != secure)SecureString이 equals를 재정의하지 않기 때문에 항상 false 이기 때문 입니다. 이견있는 사람?
simonalexander2005


2

나는이 방법을 사용하고 암호 상자를 전달했지만 이것이 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);
}

이것은 MVVM 패턴을 명백히 위반하는 것입니다. 이 패턴은 뷰 모델에서 컨트롤을 처리 할 수 ​​없습니다.
BionicCode

2

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

  • 일반 텍스트 비밀번호 속성 구현
  • 보내기 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;
            }
        }

    }
}

2

나와 같은 완전한 초보자를 위해 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);
    }
}

이것은 MVVM 패턴을 명백히 위반하는 것입니다. 이 패턴은 뷰 모델에서 컨트롤을 처리 할 수 ​​없습니다.
BionicCode

1

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

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

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

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

1

나는 다음과 같이했다 :

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

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


당신은 나에게 좋은 아이디어를 제공합니다. :)
Andre Mendonca 2018 년

1

앞에서 언급했듯이 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"
    };

의견은 긴 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
Samuel Liew

1

Windows 범용 앱에서

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

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


1

이 구현으로 인한 위험을 알고있는 사람은 ViewModel에 비밀번호를 동기화하기 위해 간단히 Mode = OneWayToSource 를 추가하십시오 .

XAML

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

왜 안 해 OneWayToSource?
BK

@BK 내 답변을 수정했습니다. 감사.
Kevin

1
모드가 바인딩 괄호 안에 없어야합니까?
Mat

@ 매트 p. 감사.
Kevin

1

여기에 내가 가져 가라.

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

0

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

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


2
WAF의 Pop3SettingsView에서 사용되는 방식은 재미 있습니다. PasswordBox passwordBox = (비밀번호) 발신자; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } ViewModel의 Pop3Password는 문자열 속성입니다. 그래서, 그것도 안전하지 않습니다 .. 첨부 된 속성을 사용하는 것이 좋습니다
Michael Sync

0

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

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


0

아직 언급되지 않은 간결한 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 친화적으로 만들 수 있습니다.

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


0

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

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

DevExpress 웹 사이트의 솔루션

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


0

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

;) 쉬워요!


0

매우 간단합니다. 암호에 대한 다른 속성을 만들고 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;
        }
    }


비밀번호 상자를 바인딩 할 수없는 이유는 비밀번호를 명확한 문자열로 저장하지 않기 때문입니다. 문자열은 변경할 수 없으며 메모리에 얼마나 오래 머무를 지 확실하지 않습니다.
랜스

0

음 내 대답은 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>

0

SecureString부착 된 동작을 사용하여 뷰 모델로 전송ICommand

MVVM을 구현할 때 코드 숨김에는 아무런 문제가 없습니다. MVVM은 뷰를 모델 / 비즈니스 로직과 분리하는 것을 목표로하는 아키텍처 패턴입니다. MVVM은이 목표를 재현 가능한 방식 (패턴)으로 달성하는 방법을 설명합니다. 뷰를 구성하거나 구현하는 방법과 같은 구현 세부 정보는 중요하지 않습니다. 경계를 그리고 뷰, 뷰 모델 및이 패턴의 용어로 모델을 정의합니다.

MVVM은 언어 (XAML 또는 C #) 또는 컴파일러 (partial 클래스)에 . 언어 독립적 인 것은 디자인 패턴의 필수 특성이며 언어 중립적이어야합니다.

그러나 코드 숨김은 XAML과 C #간에 격렬하게 배포 될 때 UI 논리를 이해하기 어렵게 만드는 단점이 있습니다. 그러나 C #에서 UI 로직 또는 템플릿, 스타일, 트리거, 애니메이션 등의 개체를 구현하는 가장 중요한 구현은 XAML을 사용하는 것보다 매우 복잡하고 읽기 쉽지 않습니다. XAML은 태그와 중첩을 사용하여 객체 계층을 시각화하는 마크 업 언어입니다. XAML을 사용하여 UI를 만드는 것이 매우 편리합니다. C # (또는 코드 숨김)에서 UI 로직을 구현하기로 선택한 경우가 있습니다. 를 처리하는 PasswordBox것이 한 예입니다.

이러한 이유로을 처리하여 PasswordBox코드 숨김 PasswordBox.PasswordChanged에서을 처리하는 것은 MVVM 패턴을 위반하지 않습니다.

명백한 위반은 컨트롤 ( PasswordBox)을 뷰 모델 에 전달하는 것 입니다. 예를 들어 PasswordBoxas 의 인스턴스를 ICommand.CommandParameter뷰 모델 로 전달하는 베이가 많은 솔루션에서이 방법을 권장합니다 . 분명히 매우 나쁘고 불필요한 추천입니다.

C # 사용에 신경 쓰지 않고 코드 숨김 파일을 깨끗하게 유지하거나 단순히 동작 / UI 논리를 캡슐화하려는 경우 항상 연결된 속성을 사용하고 연결된 동작을 구현할 수 있습니다.

일반 텍스트 암호 (실제로 나쁜 안티 패턴 및 보안 위험)에 바인딩 할 수있는 악명 높은 광범위한 도우미와 달리이 동작은 이벤트 가 발생할 때마다보기 모델에 ICommand대한 암호를 보내는 데 사용합니다 .SecureStringPasswordBoxPasswordBox.PasswordChanged

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <PasswordBox PasswordBox.Command="{Binding VerifyPasswordCommand}" />
</Window>

ViewModel.cs

public class ViewModel : INotifyPropertyChanged
{
  public ICommand VerifyPasswordCommand => new RelayCommand(VerifyPassword);

  public void VerifyPassword(object commadParameter)
  {
    if (commandParameter is SecureString secureString)
    {
      IntPtr valuePtr = IntPtr.Zero;
      try
      {
        valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
        string plainTextPassword = Marshal.PtrToStringUni(valuePtr);

        // Handle plain text password. 
        // It's recommended to convert the SecureString to plain text in the model, when really needed.
      } 
      finally 
      {
        Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
      }
    }
  }
}

PasswordBox.cs

// Attached behavior
class PasswordBox : DependencyObject
{
  #region Command attached property

  public static readonly DependencyProperty CommandProperty =
    DependencyProperty.RegisterAttached(
      "Command",
      typeof(ICommand),
      typeof(PasswordBox),
      new PropertyMetadata(default(ICommand), PasswordBox.OnSendPasswordCommandChanged));

  public static void SetCommand(DependencyObject attachingElement, ICommand value) =>
    attachingElement.SetValue(PasswordBox.CommandProperty, value);

  public static ICommand GetCommand(DependencyObject attachingElement) =>
    (ICommand) attachingElement.GetValue(PasswordBox.CommandProperty);

  #endregion

  private static void OnSendPasswordCommandChanged(
    DependencyObject attachingElement,
    DependencyPropertyChangedEventArgs e)
  {
    if (!(attachingElement is System.Windows.Controls.PasswordBox passwordBox))
    {
      throw new ArgumentException("Attaching element must be of type 'PasswordBox'");
    }

    if (e.OldValue != null)
    {
      return;
    }

    WeakEventManager<object, RoutedEventArgs>.AddHandler(
      passwordBox,
      nameof(System.Windows.Controls.PasswordBox.PasswordChanged),
      SendPassword_OnPasswordChanged);
  }

  private static void SendPassword_OnPasswordChanged(object sender, RoutedEventArgs e)
  {
    var attachedElement = sender as System.Windows.Controls.PasswordBox;
    SecureString commandParameter = attachedElement?.SecurePassword;
    if (commandParameter == null || commandParameter.Length < 1)
    {
      return;
    }

    ICommand sendCommand = GetCommand(attachedElement);
    sendCommand?.Execute(commandParameter);
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.