Jak połączyć się z PasswordBox w MVVM


251

Z wiązania z P Mam natknąć się na problem asswordBox. Wygląda na to, że stanowi to zagrożenie bezpieczeństwa, ale używam wzorca MVVM, więc chcę to ominąć. Znalazłem tutaj interesujący kod (czy ktoś używał tego lub czegoś podobnego?)

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

Technicznie wygląda świetnie, ale nie jestem pewien, jak odzyskać hasło.

Zasadniczo mam właściwości w moim LoginViewModelfor Usernamei Password. Usernamejest w porządku i działa tak jak jest TextBox.

Użyłem powyższego kodu i podałem go

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

Kiedy miałem PasswordBoxjako a, TextBoxa Binding Path=Passwordnastępnie właściwość LoginViewModelzostała zaktualizowana.

Mój kod jest bardzo prosty, w zasadzie mam Commanddla siebie Button. Kiedy naciskam, CanLoginjest wywoływany, a jeśli zwraca true, wywołuje Login.
Widzisz, sprawdzam tutaj swoją nieruchomość Username, która działa świetnie.

W Loginwyślę razem z moim naprawiać Usernamei Password, Usernamezawiera dane od mojego View, ale PasswordjestNull|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 { }
}

To właśnie robię

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

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

Mam TextBox, to nie jest problem, ale w moim jest pusty.ViewModelPassword

Czy robię coś złego lub brakuje mi kroku?

Ustawiłem punkt przerwania i na pewno kod wchodzi do statycznej klasy pomocnika, ale nigdy nie aktualizuje mojego Passwordw moim ViewModel.


3
Okazuje się, że kod nie działał, ale próbowałem tutaj alternatywnego kodu i działa idealnie. blog.functionalfun.net/2008/06/...
Mark Smith

5
Czy przekazanie całej kontroli hasła nie stoi w sprzeczności z oddzieleniem widoku od modelu widoku?

Odpowiedzi:


164

Przepraszam, ale robisz to źle.

Ludzie powinni mieć wytatuowane następujące wytyczne bezpieczeństwa na wewnętrznej stronie powiek:
Nigdy nie przechowuj haseł w postaci zwykłego tekstu w pamięci.

Powód, dla którego WPF / Silverlight PasswordBoxnie ujawnia DP dla Passwordwłaściwości, jest związany z bezpieczeństwem.
Jeśli WPF / Silverlight Passwordmiałby zachować DP , wymagałoby to od szkieletu, aby samo hasło nie było szyfrowane w pamięci. Co jest uważane za dość kłopotliwy wektor ataku bezpieczeństwa. PasswordBoxZastosowania zaszyfrowane pamięci (swego rodzaju), a jedynym sposobem, aby uzyskać dostęp do hasła jest za pośrednictwem właściwości CLR.

Sugerowałbym, aby podczas uzyskiwania dostępu do PasswordBox.Passwordwłaściwości CLR powstrzymywał się od umieszczania jej w dowolnej zmiennej lub jako wartości dla dowolnej właściwości.
Przechowywanie hasła w postaci zwykłego tekstu w pamięci RAM komputera klienta jest zabezpieczeniem nie-nie.
Pozbądź się tego, że tam public string Password { get; set; }jesteś.

Podczas uzyskiwania dostępu PasswordBox.Passwordpo prostu wyjmij go i wyślij jak najszybciej na serwer. Nie trzymaj wartości hasła w pobliżu i nie traktuj go tak, jak każdego innego tekstu na komputerze klienta. Nie przechowuj haseł tekstowych w pamięci.

Wiem, że to łamie wzorzec MVVM, ale nigdy nie powinieneś wiązać się z PasswordBox.PasswordAttached DP, przechowywać hasła w ViewModel lub innych podobnych shenaniganach.

Jeśli szukasz rozwiązania o nadmiernej architekturze, oto jedno:
1. Utwórz IHavePasswordinterfejs za pomocą jednej metody, która zwraca czysty tekst hasła.
2. mieć swój UserControlzaimplementować IHavePasswordinterfejs.
3. Zarejestruj UserControlinstancję w swoim IoC jako implementującą IHavePasswordinterfejs.
4. Gdy ma miejsce żądanie serwera wymagające twojego hasła, zadzwoń do IoC w celu IHavePasswordwdrożenia i tylko wtedy otrzymaj bardzo upragnione hasło.

Tylko moje zdanie.

- Justin


19
Czy nie możesz użyć SecureString w maszynie wirtualnej dla WPF, aby rozwiązać ten problem? Nie wydaje się, żeby było coś dla Silverlight.
Bryant,

35
Zgadzam się z twoją intencją i przesłaną wiadomością, ale twoja odpowiedź sugeruje, że ciąg hasła nigdy nie jest w pamięci, jeśli zastosujesz się do tego podejścia. Wartość hasła będzie w pamięci od momentu jego wpisania przez użytkownika. Wyeliminowanie własności przechowującej twoje hasło jest dobrym pomysłem i ograniczy kopie twojego hasła, które zostaną pozostawione do odłożenia przez moduł zbierający śmieci lub które mogą być znalezione przez inny zarządzany i niezarządzany kod działający jako część twojego programu, ale będzie nie ukrywać tego całkowicie.
IanNorton

182
W większości przypadków nie potrzebujesz takiego poziomu bezpieczeństwa. Po co utrudniać to jedno, skoro istnieje tak wiele innych sposobów kradzieży haseł? Przynajmniej WPF powinien pozwolić na użycie SecureString, jak powiedział @Bryant.
chakrit

335
Jeśli złoczyńcy mają dostęp do pamięci RAM komputera, masz większe problemy niż kradzież hasła.
Cameron MacFarland

13
Od lat korzystam z niestandardowego formantu użytkownika, który zachowuje się jak PasswordBox, ale zwraca wartość tekstową tylko jako SecureString. Tak, zapobiega to wyświetlaniu hasła przez Snoop w postaci zwykłego tekstu. Jednak wartość SecureString w postaci zwykłego tekstu można nadal dość łatwo wyodrębnić i odstrasza tylko początkujących hakerów. Jeśli twój system jest narażony na ryzyko potajemnego korzystania z rejestratorów kluczy i snifferów, takich jak Snoop, powinieneś dokonać ponownej oceny pod kątem bezpieczeństwa systemu.
Mike Christian

199

Moje 2 centy:

Kiedyś opracowałem typowe okno dialogowe logowania (pola użytkownika i hasła oraz przycisk „Ok”) przy użyciu WPF i MVVM. Rozwiązałem problem wiązania hasła, przekazując samą kontrolę PasswordBox jako parametr do polecenia dołączonego do przycisku „Ok”. Więc w widoku miałem:

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

A w ViewModel Executemetoda dołączonego polecenia była następująca:

void Execute(object parameter)
{
    var passwordBox = parameter as PasswordBox;
    var password = passwordBox.Password;
    //Now go ahead and check the user name and password
}

To nieco narusza wzorzec MVVM, ponieważ teraz ViewModel wie coś o tym, jak widok jest implementowany, ale w tym konkretnym projekcie było mnie stać. Mam nadzieję, że jest to przydatne także dla kogoś.


Cześć Konamiman, gdy wywoływana jest metoda Execute. W moim viewmodelu mam klasę User (login, pass) i polecenie uwierzytelniające się. Jak mogę użyć Execute w tym kontekście?

3
bardzo pomocne, dzięki. fyi, ktoś może być przyzwyczajony do oglądania czegoś takiego jak _loginCommand = new RelayCommand (param => Login (nazwa_użytkownika, (PasswordBox) param), param => CanLogIn);
Chuck Rostance

5
jest to dobre rozwiązanie, ale nie działa w przypadku kombinacji hasła i potwierdzenia hasła
Julien

Witaj Konamiman, używam twojego rozwiązania, ale nie działa ono w aplikacji Windows 8.1 Store. Zadałem to pytanie: stackoverflow.com/questions/26221594/...
VansFannel

2
Dzięki za to! To rozwiązało ogromny problem, który miałem z przenoszeniem danych z wątku interfejsu użytkownika do głównego wątku programu. Pamiętaj o wdrożeniu metody SecureString i ~ pozbądź się hasła tak szybko, jak to możliwe ~. Zrzuć to. Wyrzuć to. Wyczyść to. Rób to, co musisz zrobić. Upewnij się także, że implementujesz IDisposable.
Steven C. Britton

184

Może czegoś mi brakuje, ale wygląda na to, że większość z tych rozwiązań komplikuje sprawy i eliminuje bezpieczne praktyki.

Ta metoda nie narusza wzorca MVVM i zapewnia pełne bezpieczeństwo. Tak, technicznie rzecz biorąc, jest w tyle za kodem, ale nie jest niczym więcej niż powiązaniem „specjalnego przypadku”. ViewModel nadal nie ma wiedzy na temat implementacji View, co moim zdaniem ma, jeśli próbujesz przekazać PasswordBox do ViewModel.

Code Behind! = Automatyczne naruszenie MVVM. Wszystko zależy od tego, co z tym zrobisz. W tym przypadku po prostu ręcznie kodujemy powiązanie, więc jest ono uważane za część implementacji interfejsu użytkownika i dlatego jest w porządku.

W ViewModel, tylko prosta właściwość. Zrobiłem to „tylko do pisania”, ponieważ nie powinno być potrzeby pobierania go spoza ViewModel z jakiegokolwiek powodu, ale nie musi tak być. Pamiętaj, że jest to SecureString, a nie tylko ciąg znaków.

public SecureString SecurePassword { private get; set; }

W xaml konfigurujesz moduł obsługi zdarzeń PasswordChanged.

<PasswordBox PasswordChanged="PasswordBox_PasswordChanged"/>

W kodzie za:

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

Dzięki tej metodzie Twoje hasło pozostaje w SecureString przez cały czas, a zatem zapewnia maksymalne bezpieczeństwo. Jeśli naprawdę nie zależy ci na bezpieczeństwie lub potrzebujesz hasła w postaci jawnego tekstu dla wymagającej go metody (uwaga: większość metod .NET wymagających hasła obsługuje także opcję SecureString, więc może nie być tak naprawdę hasła nawet jeśli uważasz, że tak), zamiast tego możesz po prostu użyć właściwości Hasło. Lubię to:

(Właściwość ViewModel)

public string Password { private get; set; }

(Kod za)

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

Jeśli chcesz zachować silną charakterystykę, możesz zastąpić (dynamiczną) obsadę interfejsem swojego ViewModel. Ale tak naprawdę „normalne” powiązania danych również nie są silnie typowane, więc nie jest to nic wielkiego.

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

Więc najlepiej ze wszystkich światów - twoje hasło jest bezpieczne, twój ViewModel ma po prostu właściwość jak każda inna właściwość, a Twój widok jest samowystarczalny i nie wymaga żadnych zewnętrznych odniesień.


1
Ten wygląda mi dobrze! Jeśli chcesz być bardzo rygorystyczny po stronie bezpieczeństwa, nie jestem pewien, czy to by go uciszyło, ale dla mnie jest to idealny środek. dzięki!
jrich523,

3
Dzięki za praktyczność w stosunku do sztywnego dogmatu o MVVM i paranoi. Działa świetnie, dzięki.
Bruce Pierson,

2
Przykład SecureString
Ayman

1
Rzeczywiście miło. Chciałbym, żeby MS właśnie dodał do tej kontroli hasło DP typu SecureString.
Keith Hill,

1
To idealna odpowiedź, ponieważ zapewnia bezpieczeństwo i MVVM.
LoRdPMN

20

Możesz użyć tego 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>

I to polecenie wykonuje metodę:

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

3
FYIxmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAMlMAX

Bez konieczności nazywania PasswordBox: CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=PasswordBox}}"(uwaga: nie RelativeSource Self ).
wondra

To rozwiązanie narusza wzorzec MVVM.
BionicCode

13

To działa dobrze dla mnie.

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

3
Co z CommandParameter = "{Binding ElementName = MyPasswordBox, Path = SecurePassword"}?
Łukasza

2
LukeN, to nie działa (przynajmniej dla mnie). Prawdopodobnie z tego samego powodu - SecurePassword nie jest właściwością zależności.
vkrzv

Zakładając, że ICommandjest on zaimplementowany w modelu widoku, to rozwiązanie naruszałoby wzorzec MVVM.
BionicCode

9

Prostym rozwiązaniem bez naruszenia wzorca MVVM jest wprowadzenie zdarzenia (lub delegowania) w ViewModel, które zbiera hasło.

W ViewModel :

public event EventHandler<HarvestPasswordEventArgs> HarvestPassword;

z tymi EventArgs:

class HarvestPasswordEventArgs : EventArgs
{
    public string Password;
}

w Widoku zasubskrybuj wydarzenie podczas tworzenia ViewModel i wpisz wartość hasła.

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

Gdy potrzebujesz hasła, w ViewModel możesz uruchomić zdarzenie i stamtąd pobrać hasło:

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

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

LoginHelpers.Login(Username, pwargs.Password);

Jedyną rzeczą, której brakuje, jest to, że subskrybując widok do zdarzenia modelu widoku, należy użyć opcji a, WeakEventManager<TEventSource, TEventArgs>aby uniknąć wycieków pamięci. Często widok nie będzie miał takiego samego okresu życia jak model viewmodel. WeakEventManager<IViewModel, EventArgs>.AddHandler(iViewModelInstance, nameof(IViewModel.Event), eventHandlerMethod);
Todd A. Stedel,

Wolę to rozwiązanie, ponieważ jest proste, nie narusza MVVM, ma minimalny kod z tyłu, pozwala na prawidłowe użycie hasła (jeśli zamiast tego używasz „Bezpiecznego hasła”). Również teraz jest teraz łatwo zaimplementować inne metody HarvestPassword (takie jak SmartCard ....)
Matt

8

Spędziłem dużo czasu, szukając różnych rozwiązań. Nie podobał mi się pomysł dekoratorów, zachowania popsuły interfejs sprawdzania poprawności, kod za ... naprawdę?

Najlepszym z nich jest trzymanie się niestandardowej, dołączonej właściwości i wiązanie się z twoją SecureStringwłaściwością w modelu widoku. Trzymaj go tam tak długo, jak możesz. Ilekroć będziesz potrzebować szybkiego dostępu do zwykłego hasła, tymczasowo przekonwertuj je na niezabezpieczony ciąg, używając poniższego kodu:

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

Upewnij się, że zezwalasz GC na gromadzenie elementu interfejsu użytkownika, więc oprzyj się potrzebie użycia statycznego modułu obsługi zdarzeń dla PasswordChangedzdarzenia w sieci PasswordBox. Odkryłem również anomalię, w której formant nie aktualizował interfejsu użytkownika podczas korzystania z SecurePasswordwłaściwości do jego konfigurowania, dlatego Passwordzamiast tego kopiuję hasło .

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

A użycie XAML:

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

Moja właściwość w modelu widoku wyglądała następująco:

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

Jest RequiredSecureStringto prosty niestandardowy moduł sprawdzania poprawności, który ma następującą logikę:

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

Masz to. Kompletne i przetestowane czyste rozwiązanie MVVM.


7

Zamieściłem tutaj GIST , która jest wiążącym polem hasła.

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
choć nie jest to złe, tracisz możliwość ustawiania prostych atrybutów, takich jak wypełnienie i tabindex
Julien

1
Taylor, przedstawiłem sedno, aby było dostępne w odpowiedzi. (W przeciwnym razie wyglądało to jak odpowiedź tylko za pomocą linku. Po prostu staram się uniknąć tego usunięcia jako takiego.) Nie krępuj się wprowadzać treści.
Lynn Crumbling

@Julien, ale możesz to naprawić za pomocą stylów. Rozwiązuję ten problem w podobny sposób, ale używam ContentControl, możesz użyć PasswordBox jako zawartości i stylu, który pasuje do XAML. Celem tego ContentControljest po prostu zasubskrybowanie PasswordChangedwydarzenia i ujawnienie dwukierunkowej wiążącej właściwości. Podsumowując, jest to 65 linii kodu i właściwie to, co robi ta klasa dekoracji. Zobacz tutaj mój sedno następującego gist.github.com/leidegre/c7343b8c720000fe3132
John Leidegren

6

Ta implementacja jest nieco inna. Przekazujesz hasło do powiązania View przez właściwość w ViewModel, nie używa on żadnych parametrów poleceń. ViewModel pozostaje ignorantem widoku. Mam projekt VB vs 2010, który można pobrać z SkyDrive. Wpf MvvM PassWordBox Example.zip https://skydrive.live.com/redir.aspx?cid=e95997d33a9f8d73&resid=E95997D33A9F8D73!511

Sposób, w jaki używam PasswordBox w aplikacji Wpf MvvM jest dość uproszczony i działa dobrze dla mnie. Nie oznacza to, że uważam, że jest to właściwy lub najlepszy sposób. To tylko implementacja Korzystanie z PasswordBox i Wzorca MvvM.

Zasadniczo tworzysz publiczną właściwość tylko do odczytu, z którą widok może się połączyć jako PasswordBox (rzeczywista kontrola) Przykład:

Private _thePassWordBox As PasswordBox
Public ReadOnly Property ThePassWordBox As PasswordBox
    Get
        If IsNothing(_thePassWordBox) Then _thePassWordBox = New PasswordBox
        Return _thePassWordBox
    End Get
End Property

Korzystam z pola kopii zapasowej tylko w celu samodzielnego zainicjowania właściwości.

Następnie z Xaml wiążesz zawartość ContentControl lub Control Container Przykład:

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

Stamtąd masz pełną kontrolę nad hasłem. Używam również PasswordAccessor (po prostu funkcja ciągu), aby zwrócić wartość hasła podczas logowania lub cokolwiek innego, dla czego chcesz hasło. W tym przykładzie mam właściwość publiczną w ogólnym modelu obiektowym użytkownika. Przykład:

Public Property PasswordAccessor() As Func(Of String)

W obiekcie użytkownika właściwość ciągu hasła jest odczytywana tylko bez magazynu kopii zapasowych, po prostu zwraca hasło z PasswordBox. Przykład:

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

Następnie w ViewModel upewniam się, że Accessor został utworzony i ustawiony na właściwość PasswordBox.Password 'Przykład:

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

Kiedy potrzebuję ciągu Hasło powiedzieć do logowania, po prostu otrzymuję właściwość Hasło obiektów użytkownika, która naprawdę wywołuje funkcję, aby pobrać hasło i zwrócić je, a następnie rzeczywiste hasło nie jest przechowywane przez obiekt użytkownika. Przykład: byłby w 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

Że należy to zrobić. ViewModel nie potrzebuje żadnej wiedzy o kontrolkach View. Widok po prostu wiąże się z właściwością w ViewModel, nie różni się niczym od widoku Powiązanie z obrazem lub innym zasobem. W tym przypadku ten zasób (Właściwość) jest po prostu kontrolą użytkownika. Umożliwia testowanie, ponieważ ViewModel tworzy właściwość i jest jej właścicielem, a właściwość jest niezależna od widoku. Jeśli chodzi o bezpieczeństwo, nie wiem, jak dobra jest ta implementacja. Ale za pomocą funkcji wartość nie jest przechowywana w samej właściwości, do której właściwość ma dostęp.


6

Aby rozwiązać problem OP bez zerwania MVVM, użyłbym niestandardowego konwertera wartości i opakowania wartości (hasła), które należy pobrać z pola hasła.

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

W modelu widoku:

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

Ponieważ używa modelu widoku IWrappedParameter<T>, nie musi on posiadać żadnej wiedzy na temat PasswordBoxWrapperani PasswordBoxConverter. W ten sposób możesz odizolować PasswordBoxobiekt od modelu widoku i nie przerywać wzorca MVVM.

W widoku:

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

bardzo eleganckie rozwiązanie imo. na tym oparłem moje. jedyna różnica: przekazuję SecureString SecurePassword do funkcji logowania zamiast ciągu Hasło. aby nie było żadnych niezaszyfrowanych ciągów z hasłem przelatującym wokół pamięci.
zadzwoń do mnie marchewka,

Minęło trochę czasu, ale wydaje mi się, że nie mogę tego uruchomić z powodu mojej RelayCommand. czy miałbyś coś przeciwko dodaniu swojego?
Ecnerwal,

5

Chociaż zgadzam się, że ważne jest, aby unikać przechowywania hasła w dowolnym miejscu, nadal potrzebuję możliwości tworzenia modelu widoku bez widoku i wykonywania na nim testów.

Rozwiązaniem, które działało dla mnie, było zarejestrowanie funkcji PasswordBox.Password w modelu widoku i wywołanie jej przez model widoku podczas wykonywania kodu logowania.

To nie znaczy, linię kodu w kodzie widoku za.

Tak więc w moim Login.xaml mam

<PasswordBox x:Name="PasswordBox"/>

i w Login.xaml.cs mam

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

następnie w LoginViewModel.cs mam zdefiniowane PasswordHandler

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

a kiedy trzeba się zalogować, kod wywołuje moduł obsługi, aby uzyskać hasło z widoku ...

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

W ten sposób, gdy chcę przetestować model widoku, mogę po prostu ustawić PasswordHandler na anonimową metodę, która pozwala mi podać dowolne hasło, którego chcę użyć w teście.


4

Pomyślałem, że wrzucę swoje rozwiązanie do miksu, ponieważ jest to tak powszechny problem ... a posiadanie wielu opcji jest zawsze dobrą rzeczą.

Ja po prostu zapakowane PasswordBoxw sposób UserControli wdrożył DependencyPropertyaby móc związać. Robię wszystko, co w mojej mocy, aby uniknąć zapisywania wyraźnego tekstu w pamięci, więc wszystko odbywa się za pomocą a SecureStringi PasswordBox.Passwordwłasności. Podczas foreachpętli każda postać zostaje odsłonięta, ale jest bardzo krótka. Szczerze mówiąc, jeśli martwisz się, że twoja aplikacja WPF zostanie naruszona w wyniku tego krótkiego ujawnienia, masz większe problemy z bezpieczeństwem, które należy rozwiązać.

Piękno tego polega na tym, że nie łamiesz żadnych zasad MVVM, nawet tych „purystycznych”, ponieważ jest to UserControltak, że można mieć w tyle kod. Kiedy go używasz, możesz mieć czystą komunikację pomiędzy Viewi ViewModelbez VideModelświadomości jakiejkolwiek części Viewlub źródła hasła. Wystarczy upewnić się, że wiązanie się SecureStringw twojej 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 (Wersja 1 - Brak obsługi wiązania dwukierunkowego).

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

Zastosowanie wersji 1:

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

BindablePasswordBox.xaml.cs (Wersja 2 - Obsługuje dwukierunkowe wiązanie).

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

Zastosowanie wersji 2:

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

Próbowałem to zaimplementować, ale podczas aktualizacji hasła w interfejsie pojawia się nieskończona pętla; ponieważ if (Password != secure)zawsze będzie fałszem, ponieważ SecureString nie zastępuje równości. jakieś pomysły?
simonalexander2005


2

Użyłem tej metody i przekazałem pole hasła, chociaż narusza to MVVM, było to dla mnie bardzo ważne, ponieważ użyłem formantu zawartości z szablonem danych do logowania w mojej powłoce, co jest złożonym środowiskiem powłoki. Więc dostęp do kodu za powłoką byłby bzdurą.

Przekazywanie skrzynki hasła, jak sądzę, jest tak samo jak uzyskiwanie dostępu do kodu z tyłu, o ile wiem. Zgadzam się z hasłami, nie przechowuj w pamięci itp. W tej implementacji nie mam właściwości hasła w widoku modelu.

Polecenie przycisku

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

ViewModel

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

Jest to wyraźne naruszenie wzorca MVVM. Wzorzec nie pozwala obsługiwać elementów sterujących w modelu widoku.
BionicCode

2

Dla mnie obie te rzeczy są złe:

  • Implementowanie właściwości hasła w postaci zwykłego tekstu
  • Wysyłanie PasswordBoxjako parametru polecenia do ViewModel

Przeniesienie SecurePassword (instancja SecureString) zgodnie z opisem Steve'a w CO wydaje się dopuszczalne. Wolę Behaviorskodować z tyłu, a także miałem dodatkowy wymóg resetowania hasła z viewmodelu.

Xaml ( Passwordjest właściwością ViewModel):

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

Zachowanie:

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

Dla kompletnych początkujących, takich jak ja, tutaj jest kompletna działająca próbka tego, co Konamimansugerowano powyżej. Dzięki Konamiman.

XAML

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

ViewModel

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

Jest to wyraźne naruszenie wzorca MVVM. Wzorzec nie pozwala obsługiwać elementów sterujących w modelu widoku.
BionicCode

1

Jak widać wiążę się z hasłem, ale może wiąże się z klasą statyczną.

Jest to nieruchomość przyłączona . Tego rodzaju właściwość można zastosować do dowolnego rodzaju DependencyObject, nie tylko typu, w którym jest zadeklarowana. Więc chociaż jest zadeklarowany w PasswordHelperklasie statycznej, jest stosowany doPasswordBox na którym go używasz.

Aby użyć tej dołączonej właściwości, wystarczy powiązać ją z Passwordwłaściwością w swoim ViewModel:

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

1

Zrobiłem jak:

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>

DO#:

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

Mi to pasuje!


Dajesz mi fajny pomysł. :)
Andre Mendonca,

1

Jak wspomniano wcześniej, VM nie powinna wiedzieć o widoku, ale przekazanie całego PasswordBox wygląda na najprostsze podejście. Może więc zamiast rzutować parametr na PasswordBox użyj Reflection, aby wyodrębnić z niego właściwość Password. W tym przypadku VM oczekuje pewnego rodzaju kontenera haseł z właściwością Hasło (używam RelayCommands z MVMM Light-Toolkit):

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

Można go łatwo przetestować za pomocą anonimowej klasy:

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

Komentarze nie są przeznaczone do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Samuel Liew

1

W Windows Universal App

możesz użyć tego kodu z właściwością „Hasło” i powiązaniem z modelView

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


1

Dla każdego, kto zdaje sobie sprawę z zagrożeń, jakie nakłada ta implementacja, aby zsynchronizować hasło z ViewModel, po prostu dodaj Mode = OneWayToSource .

XAML

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

Dlaczego po prostu nie zrobić OneWayToSource?
BK

@BK Edytowałem moją odpowiedź. Dzięki.
Kevin

1
czy Tryb nie powinien znajdować się w wiązaniach klamrowych?
Mat

@Mat Yap. Dzięki.
Kevin

1

Oto moje zdanie na ten temat:

  1. Użycie dołączonej właściwości do powiązania hasła nie pozwala na zabezpieczenie hasła. Właściwość Hasło pola hasła nie może zostać powiązana z jakiegoś powodu.

  2. Przekazanie pola hasła jako parametru polecenia uświadomi ViewModel kontrolę. To nie zadziała, jeśli planujesz, aby Twoja platforma ViewModel była wielokrotnego użytku. Nie informuj maszyny wirtualnej o swoim widoku ani żadnych innych kontrolkach.

  3. Nie sądzę, aby wprowadzenie nowej właściwości, interfejsu, subskrybowanie zdarzeń zmieniających hasło lub innych skomplikowanych rzeczy było konieczne do wykonania prostego zadania podania hasła.

XAML

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

Kod z tyłu - użycie kodu z tyłu niekoniecznie narusza MVVM. Dopóki nie wprowadzisz w to żadnej logiki biznesowej.

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

ViewModel

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

0

Rozwiązanie dla PasswordBox znajduje się w przykładowej aplikacji ViewModel w WPF Application Framework (WAF) .

Jednak Justin ma rację. Nie podawaj hasła jako zwykłego tekstu między View a ViewModel. Zamiast tego użyj SecureString (zobacz MSDN PasswordBox).


2
Sposób, który jest używany w Pop3SettingsView WAF jest zabawny. PasswordBox passwordBox = (PasswordBox) nadawca; if (ViewModel! = null) {ViewModel.Pop3Password = passwordBox.Password; } Pop3Password of ViewModel jest właściwością string. więc nie jest też bezpieczny. lepiej skorzystać z dołączonej właściwości
Michael Sync

0

Użyłem sprawdzania uwierzytelnienia, a następnie podrzędnego wywołanego przez klasę mediatora do widoku (który również implementuje sprawdzenie uwierzytelnienia), aby zapisać hasło do klasy danych.

To nie jest idealne rozwiązanie; rozwiązało to jednak mój problem braku możliwości przeniesienia hasła.


0

Używam zwięzłego, przyjaznego dla MVVM rozwiązania, o którym jeszcze nie wspomniano. Najpierw nazywam PasswordBox w XAML:

<PasswordBox x:Name="Password" />

Następnie dodaję jedno wywołanie metody do konstruktora widoku:

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

I to wszystko. Wyświetl model otrzyma powiadomienie, gdy zostanie dołączone do widoku za pośrednictwem DataContext, a kolejne powiadomienie, gdy zostanie odłączone. Treść tego powiadomienia można konfigurować za pomocą lambdas, ale zwykle jest to po prostu setter lub wywołanie metody w modelu widoku, przekazując problematyczne sterowanie jako parametr.

Można bardzo łatwo uczynić go przyjaznym dla MVVM dzięki interfejsowi wyświetlania zamiast kontroli potomnej.

Powyższy kod opiera się na klasie pomocniczej opublikowanej na moim blogu.


0

Spędziłem wieki próbując sprawić, by to zadziałało. W końcu zrezygnowałem i po prostu użyłem PasswordBoxEdit z DevExpress.

Jest to najprostsze rozwiązanie w historii, ponieważ pozwala na wiązanie bez wykonywania okropnych sztuczek.

Rozwiązanie na stronie DevExpress

Dla przypomnienia, nie jestem w żaden sposób związany z 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);
        }
    }
}

;) łatwo!


0

To jest bardzo proste . Utwórz kolejną właściwość hasła i powiąż ją za pomocą TextBox

Ale wszystkie operacje wprowadzania są wykonywane z rzeczywistą właściwością hasła

prywatny ciąg _Password;

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

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

            return szChar;
        }

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

ciąg publiczny Hasło {get {return _Password; }

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


Powodem, dla którego pole hasła nie jest wiążące, jest to, że nie chcemy przechowywać hasła w postaci czystego ciągu. Ciąg jest niezmienny i nie jesteśmy pewni, jak długo pozostanie w pamięci.
Lance

0

cóż, moja odpowiedź jest prostsza tylko dla wzoru MVVM

w klasie viewmodel

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;

}

właściwość password funkcji PasswordBox, która wygrywa, lub WatermarkPasswordBox, którą zapewnia XCeedtoolkit, generuje RoutedEventArgs, dzięki czemu można ją powiązać.

teraz w widoku 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>

lub

<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

Wyślij SecureStringdo modelu widoku za pomocą Attached Behavior iICommand

W implementacji MVVM nie ma nic złego w kodowaniu. MVVM to wzorzec architektoniczny, którego celem jest oddzielenie widoku od modelu / logiki biznesowej. MVVM opisuje, jak osiągnąć ten cel w powtarzalny sposób (wzorzec). Nie dbają o szczegóły implementacji, takie jak struktura lub implementacja widoku. Po prostu wyznacza granice i określa, co to jest widok, model widoku i co model pod względem terminologii tego wzorca.

MVVM nie dba o język (XAML lub C #) ani kompilator ( partialklasy). Niezależność językowa jest obowiązkową cechą wzoru - musi być neutralna językowo.

Jednak kodowanie ma pewne wady, takie jak utrudnienie zrozumienia logiki interfejsu użytkownika, gdy jest ona rozproszona między XAML i C #. Ale najważniejsze implementowanie logiki interfejsu użytkownika lub obiektów, takich jak szablony, style, wyzwalacze, animacje itp. W języku C # jest bardzo złożone i brzydkie / mniej czytelne niż przy użyciu XAML. XAML to język znaczników, który wykorzystuje tagi i zagnieżdżanie do wizualizacji hierarchii obiektów. Tworzenie interfejsu użytkownika za pomocą XAML jest bardzo wygodne. Chociaż są sytuacje, w których dobrze jest wybrać logikę interfejsu użytkownika w języku C # (lub z tyłu kodu). ObsługaPasswordBox jest jednym z przykładów.

Z tego powodu obsługa PasswordBoxkodu z tyłu za pomocąPasswordBox.PasswordChanged , nie stanowi naruszenia wzorca MVVM.

Oczywistym naruszeniem byłoby przekazanie kontrolki (the PasswordBox) do modelu widoku. Wiele rozwiązań poleca to np. Zatoka przechodząc przez instancję PasswordBoxasICommand.CommandParameter do modelu widoku. Oczywiście bardzo zła i niepotrzebna rekomendacja.

Jeśli nie obchodzi Cię używanie C #, ale po prostu chcesz utrzymać plik za kodem w czystości lub po prostu chcesz zawrzeć logikę zachowania / interfejsu użytkownika, zawsze możesz skorzystać z dołączonych właściwości i zaimplementować załączone zachowanie.

W przeciwieństwie do niesławnego pomocnika o szerokim zasięgu, który umożliwia powiązanie z hasłem zwykłego tekstu (naprawdę złe anty-wzorzec i ryzyko bezpieczeństwa), to zachowanie używa ICommanddo wysyłania hasła SecureStringdo modelu widoku, ilekroć PasswordBoxpodnosi to PasswordBox.PasswordChangedzdarzenie.

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);
  }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.