종속성 속성 보안(WPF .NET)

WPF(Windows Presentation Foundation) 속성 시스템을 통해 읽기-쓰기 종속성 속성에 액세스하면 해당 속성을 효과적으로 공용 속성으로 만들 수 있습니다. 따라서 읽기-쓰기 종속성 속성 값에 대한 보안을 보장할 수 없습니다. WPF 속성 시스템은 읽기 전용 종속성 속성에 대해 더 많은 보안을 제공하므로 쓰기 액세스를 제한할 수 있습니다.

중요

.NET 7 및 .NET 6에 관한 데스크톱 가이드 설명서는 제작 중입니다.

속성 래퍼의 액세스 및 보안

CLR(공용 언어 런타임) 속성 래퍼는 일반적으로 속성 값 가져오기 또는 설정을 간소화하기 위해 읽기-쓰기 종속성 속성 구현에 포함됩니다. 포함된 경우, CLR 속성 래퍼는 기본 종속성 속성과 상호 작용하는 GetValueSetValue 정적 호출을 구현하는 편리한 메서드입니다. 기본적으로 CLR 속성 래퍼는 종속성 속성을 프라이빗 필드가 아닌 종속성 속성으로 지원되는 CLR 속성으로 노출합니다.

보안 메커니즘을 적용하고 CLR 속성 래퍼에 대한 액세스를 제한하면 편리한 메서드의 사용을 방지할 수는 있겠지만 이러한 기법은 GetValue 또는 SetValue에 대한 직접 호출을 방지할 수 없습니다. 즉, 읽기-쓰기 종속성 속성은 항상 WPF 속성 시스템을 통해 액세스할 수 있습니다. 읽기-쓰기 종속성 속성을 구현하는 경우, CLR 속성 래퍼에 대한 액세스를 제한하지 마십시오. 그 대신, 호출자가 종속성 속성의 실제 액세스 수준을 인식할 수 있도록 CLR 속성 래퍼를 공용 멤버로 선언하세요.

종속성 속성의 속성 시스템 노출

WPF 속성 시스템은 DependencyProperty 식별자를 통해 읽기-쓰기 종속성 속성에 대한 액세스를 제공합니다. 식별자는 GetValueSetValue 호출에서 사용할 수 있습니다. 정적 식별자 필드가 비공개인 경우에도 속성 시스템의 여러 측면은 클래스 또는 파생 클래스의 인스턴스에 있는 그대로 DependencyProperty를 반환합니다. 예를 들어 GetLocalValueEnumerator 메서드는 로컬로 설정된 값을 가진 종속성 속성 인스턴스에 대한 식별자를 반환합니다. 또한 값이 변경된 종속성 속성에 대한 DependencyProperty 식별자를 보고하는 이벤트 데이터를 수신하도록 OnPropertyChanged 가상 메서드를 재정의할 수 있습니다. 호출자가 읽기-쓰기 종속성 속성의 실제 액세스 수준을 인식하도록 하려면 해당 식별자 필드를 공용 멤버로 선언하세요.

참고

종속성 속성 식별자 필드를 private으로 선언하면 읽기-쓰기 종속성 속성에 액세스할 수 있는 방법의 수가 줄어들지만 CLR 언어 정의에 따라 해당 속성은 비공개가 아닙니다.

유효성 검사 보안

DemandValidateValueCallback에 적용하고 Demand 실패 시 유효성 검사가 실패할 것으로 예상하는 것은 속성 값 변경을 제한하기 위한 적절한 보안 메커니즘이 아닙니다. 또한 악의적인 호출자가 애플리케이션 도메인 내에서 작동하는 경우, ValidateValueCallback을 통해 적용되는 새 값 무효화는 그러한 호출자에 의해 표시되지 않을 수도 있습니다.

읽기 전용 종속성 속성에 대한 액세스

액세스를 제한하려면 RegisterReadOnly 메서드를 호출하여 속성을 읽기 전용 종속성 속성으로 등록합니다. 이 RegisterReadOnly 메서드는 비공개 클래스 필드에 할당할 수 있는 DependencyPropertyKey를 반환합니다. 읽기 전용 종속성 속성의 경우, WPF 속성 시스템은 DependencyPropertyKey에 대한 참조가 있는 사용자에게만 쓰기 액세스를 제공합니다. 이 동작을 설명하기 위해 아래의 테스트 코드는

  • 읽기-쓰기 종속성 속성과 읽기 전용 종속성 속성을 모두 구현하는 클래스를 인스턴스화합니다.
  • 각 식별자에 private 액세스 한정자를 할당합니다.
  • get 접근자만 구현합니다.
  • GetLocalValueEnumerator 메서드를 사용하여 WPF 속성 시스템을 통해 기본 종속성 속성에 액세스합니다.
  • GetValueSetValue를 호출하여 각 종속성 속성 값에 대한 액세스를 테스트합니다.
    /// <summary>
    ///  Test get/set access to dependency properties exposed through the WPF property system.
    /// </summary>
    public static void DependencyPropertyAccessTests()
    {
        // Instantiate a class that implements read-write and read-only dependency properties.
        Aquarium _aquarium = new();
        // Access each dependency property using the LocalValueEnumerator method.
        LocalValueEnumerator localValueEnumerator = _aquarium.GetLocalValueEnumerator();
        while (localValueEnumerator.MoveNext())
        {
            DependencyProperty dp = localValueEnumerator.Current.Property;
            string dpType = dp.ReadOnly ? "read-only" : "read-write";
            // Test read access.
            Debug.WriteLine($"Attempting to get a {dpType} dependency property value...");
            Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}");
            // Test write access.
            try
            {
                Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2...");
                _aquarium.SetValue(dp, 2);
            }
            catch (InvalidOperationException e)
            {
                Debug.WriteLine(e.Message);
            }
            finally
            {
                Debug.WriteLine($"Value ({dpType}): {(int)_aquarium.GetValue(dp)}");
            }
        }

        // Test output:

        // Attempting to get a read-write dependency property value...
        // Value (read-write): 1
        // Attempting to set a read-write dependency property value to 2...
        // Value (read-write): 2

        // Attempting to get a read-only dependency property value...
        // Value (read-only): 1
        // Attempting to set a read-only dependency property value to 2...
        // 'FishCountReadOnly' property was registered as read-only
        // and cannot be modified without an authorization key.
        // Value (read-only): 1
    }
}

public class Aquarium : DependencyObject
{
    public Aquarium()
    {
        // Assign locally-set values.
        SetValue(FishCountProperty, 1);
        SetValue(FishCountReadOnlyPropertyKey, 1);
    }

    // Failed attempt to restrict write-access by assigning the
    // DependencyProperty identifier to a non-public field.
    private static readonly DependencyProperty FishCountProperty =
        DependencyProperty.Register(
          name: "FishCount",
          propertyType: typeof(int),
          ownerType: typeof(Aquarium),
          typeMetadata: new PropertyMetadata());

    // Successful attempt to restrict write-access by assigning the
    // DependencyPropertyKey to a non-public field.
    private static readonly DependencyPropertyKey FishCountReadOnlyPropertyKey =
        DependencyProperty.RegisterReadOnly(
          name: "FishCountReadOnly",
          propertyType: typeof(int),
          ownerType: typeof(Aquarium),
          typeMetadata: new PropertyMetadata());

    // Declare public get accessors.
    public int FishCount => (int)GetValue(FishCountProperty);
    public int FishCountReadOnly => (int)GetValue(FishCountReadOnlyPropertyKey.DependencyProperty);
}
    ''' <summary>
    ''' ' Test get/set access to dependency properties exposed through the WPF property system.
    ''' </summary>
    Public Shared Sub DependencyPropertyAccessTests()
        ' Instantiate a class that implements read-write and read-only dependency properties.
        Dim _aquarium As New Aquarium()
        ' Access each dependency property using the LocalValueEnumerator method.
        Dim localValueEnumerator As LocalValueEnumerator = _aquarium.GetLocalValueEnumerator()
        While localValueEnumerator.MoveNext()
            Dim dp As DependencyProperty = localValueEnumerator.Current.[Property]
            Dim dpType As String = If(dp.[ReadOnly], "read-only", "read-write")
            ' Test read access.
            Debug.WriteLine($"Attempting to get a {dpType} dependency property value...")
            Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}")
            ' Test write access.
            Try
                Debug.WriteLine($"Attempting to set a {dpType} dependency property value to 2...")
                _aquarium.SetValue(dp, 2)
            Catch e As InvalidOperationException
                Debug.WriteLine(e.Message)
            Finally
                Debug.WriteLine($"Value ({dpType}): {CInt(_aquarium.GetValue(dp))}")
            End Try
        End While

        ' Test output

        ' Attempting to get a read-write dependency property value...
        ' Value (read-write): 1
        ' Attempting to set a read-write dependency property value to 2...
        ' Value (read-write): 2

        ' Attempting to get a read-only dependency property value...
        ' Value (read-only): 1
        ' Attempting to set a read-only dependency property value to 2...
        ' 'FishCountReadOnly' property was registered as read-only
        ' and cannot be modified without an authorization key.
        ' Value (read-only): 1
    End Sub

End Class

Public Class Aquarium
    Inherits DependencyObject

    Public Sub New()
        ' Assign locally-set values.
        SetValue(FishCountProperty, 1)
        SetValue(FishCountReadOnlyPropertyKey, 1)
    End Sub

    ' Failed attempt to restrict write-access by assigning the
    ' DependencyProperty identifier to a non-public field.
    Private Shared ReadOnly FishCountProperty As DependencyProperty =
        DependencyProperty.Register(
            name:="FishCount",
            propertyType:=GetType(Integer),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New PropertyMetadata())

    ' Successful attempt to restrict write-access by assigning the
    ' DependencyPropertyKey to a non-public field.
    Private Shared ReadOnly FishCountReadOnlyPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly(
            name:="FishCountReadOnly",
            propertyType:=GetType(Integer),
            ownerType:=GetType(Aquarium),
            typeMetadata:=New PropertyMetadata())

    ' Declare public get accessors.
    Public ReadOnly Property FishCount As Integer
        Get
            Return GetValue(FishCountProperty)
        End Get
    End Property

    Public ReadOnly Property FishCountReadOnly As Integer
        Get
            Return GetValue(FishCountReadOnlyPropertyKey.DependencyProperty)
        End Get
    End Property

End Class

참고 항목