Partager via


SharePoint 프로젝트용 Azure 사용자 지정 클레임 공급자(2부)

최초 문서 게시일: 2012년 2월 15일 수요일

이 시리즈의 1부에서는 이 프로젝트의 목표(간략하게 설명하자면 SharePoint 사용자 지정 클레임 공급자에 대해 Windows Azure 테이블 저장소를 데이터 저장소로 사용하는 것)에 대해 대략적으로 설명했습니다. 클레임 공급자는 CASI Kit(영문일 수 있음)를 사용하여 사용자 선택(주소록) 및 입력 컨트롤 이름 확인 기능을 제공하는 데 필요한 데이터를 Windows Azure에서 검색합니다. 

시리즈의 3부에서는 SharePoint 팜에서 사용되는 모든 구성 요소를 만듭니다. 여기에는 SharePoint와 Azure 간의 모든 통신을 관리하는 CASI Kit 기반 사용자 지정 구성 요소가 포함됩니다. 또한 새 사용자에 대한 정보를 캡처하여 Azure 큐에 게시하는 사용자 지정 웹 파트도 있습니다. 그리고 마지막으로, 입력 컨트롤 및 사용자 검색 기능을 사용할 수 있도록 WCF를 통해 Azure 테이블 저장소와 통신(CASI Kit 사용자 지정 구성 요소 사용)하는 사용자 지정 클레임 공급자를 만듭니다.

2부에서는 이 시나리오를 약간 더 확대해 보겠습니다.

이러한 유형의 솔루션은 일반적인 시나리오(최소한의 작업으로 관리되는 엑스트라넷)에서도 효율적으로 사용할 수 있습니다. 파트너 또는 고객이 회사 웹 사이트에 방문하여 계정을 요청한 다음 해당 계정을 자동으로 "프로비전"할 수 있도록 하려는 경우를 예로 들 수 있습니다. 여기서 "프로비전"이란 사용자에 따라 각각 다른 의미일 수 있습니다. 여기서는 "프로비전"을 기본 시나리오로 사용하겠지만 공용 클라우드 리소스가 일부 작업을 자동으로 수행하도록 할 수도 있습니다.

먼저 여기서 개발할 클라우드 구성 요소를 살펴보겠습니다.

  • 지원할 모든 클레임 유형을 추적하는 테이블
  • 사용자 검색에 대해 모든 고유 클레임 값을 추적하는 테이블
  • 고유 클레임 값 목록에 추가되어야 하는 데이터를 보낼 수 있는 큐
  • Azure 테이블의 데이터 읽기/쓰기와 큐에 데이터 쓰기를 수행하는 일부 데이터 액세스 클래스
  • 큐에서 데이터를 읽고 고유 클레임 값 테이블을 채우는 Azure 작업자 역할
  • WCF 응용 프로그램(SharePoint 팜이 클레임 유형 목록을 가져오고, 클레임을 검색 및 확인하고, 큐에 데이터를 추가하기 위해 통신하는 데 사용하는 끝점이 됨)

다음으로는 각 구성 요소를 좀 더 자세히 살펴보겠습니다.

클레임 유형 테이블

클레임 유형 테이블에는 사용자 지정 클레임 공급자가 사용할 수 있는 모든 클레임 유형이 저장됩니다. 이 시나리오에서는 클레임 유형을 하나만 사용하며, 이 클레임은 ID 클레임(여기서는 전자 메일 주소)입니다. 다른 클레임을 사용할 수도 있지만, 시나리오를 간단하게 유지하기 위해 하나만 사용하겠습니다. Azure 테이블 저장소에서 테이블에 클래스 인스턴스를 추가할 것이므로 클레임 유형을 설명하는 클래스를 만들어야 합니다. 여기서도 Azure의 같은 테이블에 서로 다른 여러 클래스 유형의 인스턴스를 추가할 수 있지만, 역시 시나리오를 간단하게 유지하기 위해 여러 유형의 인스턴스를 추가하지는 않습니다. 이 테이블에서 사용하는 클래스는 다음과 같습니다.

namespace AzureClaimsData

{

    public class ClaimType : TableServiceEntity

    {

 

        public string ClaimTypeName { get; set; }

        public string FriendlyName { get; set; }

 

        public ClaimType() { }

 

        public ClaimType(string ClaimTypeName, string FriendlyName)

        {

            this.PartitionKey = System.Web.HttpUtility.UrlEncode(ClaimTypeName);

            this.RowKey = FriendlyName;

 

            this.ClaimTypeName = ClaimTypeName;

            this.FriendlyName = FriendlyName;

        }

    }

}

 

Azure 테이블 저장소에 대해 다룬 리소스가 많기 때문에 여기서는 Azure 테이블 저장소 작업에 대한 모든 기본 사항에 대해 다루지는 않습니다. PartitionKey 또는 RowKey의 정의나 이러한 항목을 사용하는 방법에 대해 자세히 알아보려면 로컬 Bing 검색 엔진에서 관련 내용을 검색해 보시기 바랍니다. 여기서 한 가지 짚고 넘어갈 점은, PartitionKey에 대해 저장하는 값을 URL로 인코딩한다는 것입니다. 그 이유는, 이 시나리오에서는 PartitionKey가 클레임 유형으로서 urn:foo:blah, https://www.foo.com/blah 등의 다양한 형식이 될 수 있기 때문입니다. 슬래시를 포함하는 클레임 유형의 경우에는 Azure에서 PartitionKey를 이러한 값으로 저장할 수 없습니다. 따라서 여기서는 Azure에서 인식 가능한 형식으로 클레임 유형을 인코딩합니다. 위에서 설명한 것처럼 여기서는 전자 메일 클레임을 사용하므로, 클레임의 유형은 https://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress입니다.

고유 클레임 값 테이블

고유 클레임 값 테이블에는 모든 고유 클레임 값이 저장됩니다. 이 시나리오에서는 클레임 유형을 하나(ID 클레임)만 저장하므로, 기본적으로 모든 클레임 값이 고유합니다. 그러나 여기서는 확장성과 관련된 이유로 이 방식을 선택했습니다. 예를 들어 이 솔루션에서 역할 클레임을 사용하려는 경우를 가정해 보겠습니다. 역할 클레임 "Employee" 또는 "Customer" 등을 1천 번 저장할 수는 없을 것입니다. 사용자 선택은 값을 선택할 수 있도록 값이 있는지만 확인하면 됩니다. 그 후에는 사이트에서 권한을 부여할 때 값을 사용할 수 있도록 하기만 하면 누구나 값을 사용할 수 있습니다. 이를 기반으로 할 때 고유 클레임 값을 저장하는 클래스는 다음과 같습니다.

namespace AzureClaimsData

{

    public class UniqueClaimValue : TableServiceEntity

    {

 

        public string ClaimType { get; set; }

        public string ClaimValue { get; set; }

        public string DisplayName { get; set; }

 

        public UniqueClaimValue() { }

 

        public UniqueClaimValue(string ClaimType, string ClaimValue, string DisplayName)

        {

            this.PartitionKey = System.Web.HttpUtility.UrlEncode(ClaimType);

            this.RowKey = ClaimValue;

 

            this.ClaimType = ClaimType;

            this.ClaimValue = ClaimValue;

            this.DisplayName = DisplayName;

        }

    }

}

 

이 코드에서는 두 가지 항목을 언급하고 넘어가겠습니다. 먼저, 이전 클래스와 마찬가지로 PartitionKey는 UrlEncoded 값을 사용하며 이 값은 클레임 유형이 되므로 슬래시를 포함합니다. 둘째로, Azure 테이블 저장소를 사용할 때 자주 확인할 수 있는 것처럼 Azure에는 SQL과 같은 JOIN 개념이 없기 때문에 데이터가 비정규화됩니다. 기술적으로는 LINQ에서 JOIN을 수행할 수 있지만, Azure 데이터로 작업할 때는 LINQ의 기능이 대부분 허용되지 않거나 성능이 너무 낮아서 비정규화하는 쪽이 더 쉽습니다. 이에 관해 다른 의견이 있으면 댓글로 남겨 주십시오. 의견을 알려 주시면 대단히 감사하겠습니다. 여기서는 이 클래스에 저장할 클레임 유형인 "전자 메일"을 표시 이름으로 사용합니다.

클레임 큐

클레임 큐는 매우 간단합니다. 여기서는 이 큐에 "새 사용자"에 대한 요청을 저장할 것입니다. 그러면 Azure 작업자 프로세스에서 큐의 요청을 읽은 다음 해당 데이터를 고유 클레임 값 테이블로 옮깁니다. 이렇게 하는 가장 큰 이유는, Azure 테이블 저장소 작업 시에는 대기 시간이 발생하는 경우가 많지만 큐에 항목을 포함하는 작업은 속도가 빠르기 때문입니다. 이 방식을 사용하는 경우 SharePoint 웹 사이트에 대한 영향을 최소화할 수 있습니다.

데이터 액세스 클래스

Azure 테이블 저장소 및 큐로 작업할 때 번거로운 측면 중 하나는 항상 고유한 데이터 액세스 클래스를 작성해야 한다는 점입니다. 테이블 저장소의 경우 데이터 컨텍스트 클래스와 데이터 원본 클래스를 작성해야 합니다. 여기에 대한 정보는 웹에서 확인이 가능하므로 이 게시물에서는 길게 설명하지 않습니다. 또한 쉽게 활용할 수 있도록 이 게시물에 Azure 프로젝트의 소스 코드를 첨부해 두었습니다. 

그러나 개인적인 스타일 선택과 관련하여 한 가지 지적해야 하는 중요한 사항이 있습니다. 이 게시물에서는 모든 Azure 데이터 액세스 코드를 개별 프로젝트로 나눕니다. 이렇게 하면 각 프로젝트를 자체 어셈블리로 컴파일할 수 있으며 Azure 이외의 프로젝트에서도 사용할 수 있습니다. 예를 들어 이 게시물에 업로드되어 있는 예제 코드에서는 Azure 백 엔드의 여러 부분을 테스트하는 데 사용한 Windows 양식 응용 프로그램을 확인할 수 있습니다. 이 응용 프로그램에는 일부 Azure 어셈블리와 데이터 액세스 어셈블리에 대한 참조를 포함한다는 점을 제외하고는 Azure에 대한 정보가 전혀 없습니다. 이 응용 프로그램은 해당 프로젝트에서도 사용할 수 있고, SharePoint에 대한 데이터 액세스 프런트 엔드를 수행하는 데 사용하는 WCF 프로젝트에서도 쉽게 사용할 수 있습니다.

아래에는 데이터 액세스 클래스의 몇 가지 특성이 나와 있습니다.

  • ·         이 클래스에는 반환하려는 데이터, 즉 클레임 유형 및 고유 클레임 값에 대한 별도의 "컨테이너" 클래스가 있습니다. 컨테이너 클래스란 유형이 List<>인 공용 속성이 포함된 간단한 클래스를 의미합니다. 데이터를 요청할 때는 결과의 List<>만 반환하는 것이 아니라 이 클래스 자체를 반환합니다. 그 이유는, Azure에서 List<>를 반환하면 클라이언트가 목록의 마지막 항목만 가져오기 때문입니다. 로컬에서 호스팅되는 WCF에서 같은 작업을 수행하는 경우에는 문제가 발생하지 않습니다. 따라서 이 문제를 해결하기 위해 다음과 같은 클래스에서 클레임 유형을 반환합니다.

namespace AzureClaimsData

{

    public class ClaimTypeCollection

    {

        public List<ClaimType> ClaimTypes { get; set; }

 

        public ClaimTypeCollection()

        {

            ClaimTypes = new List<ClaimType>();

        }

 

    }

}

 

고유 클레임 값 반환 클래스는 다음과 같습니다.

namespace AzureClaimsData

{

    public class UniqueClaimValueCollection

    {

        public List<UniqueClaimValue> UniqueClaimValues { get; set; }

 

        public UniqueClaimValueCollection()

        {

            UniqueClaimValues = new List<UniqueClaimValue>();

        }

    }

}

 

 

  • ·         데이터 컨텍스트 클래스는 매우 단순하며 특별할 것이 없습니다. 이 클래스는 다음과 같습니다.

 

namespace AzureClaimsData

{

    public class ClaimTypeDataContext : TableServiceContext

    {

        public static string CLAIM_TYPES_TABLE = "ClaimTypes";

 

        public ClaimTypeDataContext(string baseAddress, StorageCredentials credentials)

            : base(baseAddress, credentials)

        { }

 

 

        public IQueryable<ClaimType> ClaimTypes

        {

            get

            {

                //this is where you configure the name of the table in Azure Table Storage

                //that you are going to be working with

                return this.CreateQuery<ClaimType>(CLAIM_TYPES_TABLE);

            }

        }

 

    }

}

 

  • ·         데이터 원본 클래스에서는 약간 다른 방식으로 Azure에 연결합니다. 웹에서 확인할 수 있는 대부분의 예제에서는 특정 레지스트리 설정 클래스(정확한 이름은 생각나지 않음)를 사용하여 자격 증명을 읽으려고 합니다. 그러나 여기서는 데이터 클래스가 Azure 외부에서 작동하도록 할 것이므로 Azure 관련 컨텍스트가 없기 때문에 이 방식을 사용하는 데 문제가 있습니다. 그러므로 여기서는 프로젝트 속성에 Setting을 만들고, Azure 계정에 연결하는 데 필요한 계정 이름과 키를 이 Setting 안에 포함했습니다. 따라서 두 데이터 원본 클래스에는 모두 Azure 저장소에 대한 연결을 만들기 위한 다음과 같은 코드가 있습니다.

 

        private static CloudStorageAccount storageAccount;

        private ClaimTypeDataContext context;

 

 

        //static constructor so it only fires once

        static ClaimTypesDataSource()

        {

            try

            {

                //get storage account connection info

                string storeCon = Properties.Settings.Default.StorageAccount;

 

                //extract account info

                string[] conProps = storeCon.Split(";".ToCharArray());

 

                string accountName = conProps[1].Substring(conProps[1].IndexOf("=") + 1);

                string accountKey = conProps[2].Substring(conProps[2].IndexOf("=") + 1);

 

                storageAccount = new CloudStorageAccount(new StorageCredentialsAccountAndKey(accountName, accountKey), true);

            }

            catch (Exception ex)

            {

                Trace.WriteLine("Error initializing ClaimTypesDataSource class: " + ex.Message);

                throw;

            }

        }

 

 

        //new constructor

        public ClaimTypesDataSource()

        {

            try

            {

                this.context = new ClaimTypeDataContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);

                this.context.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(3));

            }

            catch (Exception ex)

            {

                Trace.WriteLine("Error constructing ClaimTypesDataSource class: " + ex.Message);

                throw;

            }

        }

 

  • ·         데이터 원본 클래스의 실제 구현에는 클레임 유형과 고유 클레임 값 둘 다에 대해 새 항목을 추가하기 위한 메서드가 포함되어 있습니다. 이 코드는 다음과 같이 매우 간단합니다.

 

        //add a new item

        public bool AddClaimType(ClaimType newItem)

        {

            bool ret = true;

 

            try

            {

                this.context.AddObject(ClaimTypeDataContext.CLAIM_TYPES_TABLE, newItem);

                this.context.SaveChanges();

            }

            catch (Exception ex)

            {

                Trace.WriteLine("Error adding new claim type: " + ex.Message);

                ret = false;

            }

 

            return ret;

        }

 

고유 클레임 값 데이터 원본에 대한 Add 메서드에서 주목해야 하는 한 가지 중요한 차이점은, 변경 내용을 저장할 때 예외가 발생해도 오류가 throw되거나 false가 반환되지 않는다는 것입니다. 그 이유는 해당 사용자를 실수로 완전히 신뢰하거나 등록을 여러 번 시도하기 때문입니다. 그러나 사용자의 전자 메일 클레임 레코드를 가져온 후에 추가하려고 하면 예외가 throw됩니다. Azure에서는 강력한 형식의 예외가 제공되지 않으며, 추적 로그에 의미 없는 내용이 채워지는 것을 원치 않으므로 이러한 상황을 염려하지 않아도 됩니다.

  • ·         클레임 검색 작업은 좀 더 독특하지만, 이는 LINQ에서 수행할 수 있는 몇 가지 작업(LINQ에서 Azure를 통해서는 수행할 수 없음)을 표시한다는 점에 국한됩니다. 아래에 해당 코드와 몇 가지 선택 사항에 대한 설명이 나와 있습니다.

 

        public UniqueClaimValueCollection SearchClaimValues(string ClaimType, string Criteria, int MaxResults)

        {

            UniqueClaimValueCollection results = new UniqueClaimValueCollection();

            UniqueClaimValueCollection returnResults = new UniqueClaimValueCollection();

 

            const int CACHE_TTL = 10;

 

            try

            {

                //look for the current set of claim values in cache

                if (HttpRuntime.Cache[ClaimType] != null)

                    results = (UniqueClaimValueCollection)HttpRuntime.Cache[ClaimType];

                else

                {

                    //not in cache so query Azure

 

                    //Azure doesn't support starts with, so pull all the data for the claim type

                    var values = from UniqueClaimValue cv in this.context.UniqueClaimValues

                                  where cv.PartitionKey == System.Web.HttpUtility.UrlEncode(ClaimType)

                                  select cv;

 

                    //you have to assign it first to actually execute the query and return the results

                    results.UniqueClaimValues = values.ToList();

 

                    //store it in cache

                    HttpRuntime.Cache.Add(ClaimType, results, null,

                        DateTime.Now.AddHours(CACHE_TTL), TimeSpan.Zero,

                        System.Web.Caching.CacheItemPriority.Normal,

                        null);

                }

 

                //now query based on criteria, for the max results

                returnResults.UniqueClaimValues = (from UniqueClaimValue cv in results.UniqueClaimValues

                           where cv.ClaimValue.StartsWith(Criteria)

                           select cv).Take(MaxResults).ToList();

            }

            catch (Exception ex)

            {

                Trace.WriteLine("Error searching claim values: " + ex.Message);

            }

 

            return returnResults;

        }

 

첫 번째로 주목할 점은, Azure 데이터에 대해 StartsWith를 사용할 수 없다는 것입니다. 즉, 모든 데이터를 로컬로 검색한 후에 StartsWith 식을 사용해야 합니다. 모든 데이터를 검색하려면 비용이 많이 들기 때문에(테이블 검색을 통해 모든 행 검색) 이 작업은 한 번만 수행한 다음 데이터를 캐시합니다. 이렇게 하면 10분마다 "실제" 회수만 수행하면 됩니다. 그러나 이 시간 동안 사용자가 추가되는 경우에는 캐시가 만료되고 모든 데이터를 다시 검색할 때까지 사용자 검색에 해당 사용자가 표시되지 않는다는 단점이 있습니다. 따라서 결과를 확인할 때 이 점을 기억해야 합니다.

실제로 데이터 집합을 가져온 후에는 StartsWith를 수행할 수 있으며 반환할 레코드의 양도 제한할 수 있습니다. 기본적으로 SharePoint에서는 사용자 검색에 레코드가 200개까지만 표시되기 때문에 이 메서드를 호출할 때도 최대 레코드 수로 200을 설정합니다. 그러나 각 사용자가 상황에 맞는 모든 작업을 수행할 수 있도록 여기서는 StartsWith를 매개 변수로 포함했습니다.

큐 액세스 클래스

솔직히 이 클래스에는 특별히 언급할 사항이 없습니다. 큐에서 메시지를 추가 및 삭제하고 읽기 위한 기본 메서드만 몇 개 추가하면 됩니다.

Azure 작업자 역할

작업자 역할 역시 그다지 많은 정보를 기술하지는 않습니다. 이 역할은 10초마다 작동되어 큐에 새 메시지가 있는지 확인합니다. 이를 위해 큐 액세스 클래스를 호출합니다. 큐에 새 항목이 있으면 세미콜론으로 구분된 콘텐츠를 개별 구성 부분으로 분할하고 UniqueClaimValue 클래스의 새 인스턴스를 만든 다음 고유 클레임 값 테이블에 해당 인스턴스 추가를 시도합니다. 그 후에는 큐에서 메시지를 삭제하고 다음 항목으로 넘어가며, 이러한 방식으로 한 번에 읽을 수 있는 최대 항목 수(32개)까지 또는 더 이상 메시지가 남아 있지 않을 때까지 해당 과정을 계속합니다.

WCF 응용 프로그램

앞에서 설명한 것처럼 SharePoint 코드는 WCF 응용 프로그램과 통신하여 큐에 항목을 추가하고, 클레임 유형 목록을 가져오고, 클레임 값을 검색하거나 확인합니다. 적절하게 신뢰되는 응용 프로그램과 마찬가지로, WCF 응용 프로그램과 이 응용 프로그램을 호출하는 SharePoint 팜 간에도 트러스트가 설정됩니다. 따라서 데이터 요청 시 토큰 스푸핑이 차단됩니다. 현재 WCF 자체에서 구현되는 상세한 보안은 없습니다. 이 시나리오에서는 완결된 솔루션을 위해 WCF를 로컬 웹 서버에서 먼저 테스트한 다음 Azure로 이동하여 다시 테스트함으로써 모든 항목이 작동함을 확인했습니다.

지금까지 이 솔루션에 포함된 Azure 구성 요소의 기본 사항에 대해 살펴보았습니다. 이 배경 설명을 통해, 실제로 가동되는 모든 구성 요소의 정의 및 해당 사용법에 대해 파악하실 수 있기를 바랍니다. 다음 게시물에서는 SharePoint 사용자 지정 클레임 공급자에 대해 설명하고, "완성 인도" 엑스트라넷 솔루션용으로 이러한 구성 요소를 모두 연결하는 방법에 대해 살펴봅니다. 이 게시물에 첨부된 파일에는 데이터 액세스 클래스, 테스트 프로젝트, Azure 프로젝트, 작업자 역할 및 WCF 프로젝트의 소스 코드가 모두 포함되어 있습니다. 또한 이 게시물의 복사본도 Word 문서로 포함되어 있으므로 이 사이트에서 렌더링된 형태의 콘텐츠를 통해서는 명확하게 파악하기가 어려울 수도 있는 솔루션 설계 의도를 보다 쉽게 이해하실 수 있을 것입니다.

이 문서는 번역된 블로그 게시물입니다. 원본 문서는 The Azure Custom Claim Provider for SharePoint Project Part 2를 참조하십시오.