SAML 클레임, SharePoint, WCF, Windows 토큰 서비스에 대한 클레임 및 제한된 위임을 사용하여 SQL Server 액세스

최초 문서 게시일: 2011년 8월 7일 일요일

이 게시물에서 설명할 관련 기술을 모두 포함하다 보니 제목이 좀 길군요. 제목에 포함된 분야와 관련하여 최근 다양한 논의가 이뤄지고 있는데, 실제로 가장 중요한 요소는 SAML 클레임 사용자와 Windows 컨텍스트를 통해 다른 응용 프로그램에 액세스하는 방법일 것입니다. SharePoint 2010에서는 소수의 서비스 응용 프로그램을 사용하는 Windows 클레임 사용자에 한해 Windows 토큰 서비스에 대한 클레임(아래에서는 c2wts로 지칭함)을 제한적으로 사용할 수 있습니다. 그러면 유효한 UPN 클레임을 포함하는 SAML 클레임 사용자를 사용할 수 없는 이유는 무엇일까요? 기술적으로는 SAML 클레임 사용자를 사용할 수 없는 이유가 없습니다. 그러나 인증 유형의 제한과 인증을 사용할 수 있는 서비스 응용 프로그램이 제한되므로, SAML 사용자를 기본 Windows 계정으로 다른 응용 프로그램에 연결하는 방법을 고안해야 하는 경우가 많습니다. 이 게시물에서는 이를 위한 기본적인 방법을 소개합니다.

이 시나리오에서 기본적으로 사용할 방법은 다른 응용 프로그램(여기서는 SQL Server)의 데이터에 대한 모든 최종 사용자 요청을 처리하는 WCF 서비스 응용 프로그램을 만드는 것입니다. 따라서 SharePoint 사이트를 방문하는 SAML 사용자를 만든 다음, SQL Server에서 데이터를 검색할 때 해당 SAML 사용자의 Windows 계정으로 요청을 수행합니다. 참고: 이 문서에서는 SAML 클레임 사용자에 대해 설명하지만, 이와 정확히 동일한 방법을 Windows 클레임 사용자에 대해서도 사용할 수 있습니다. Windows 클레임 사용자는 로그인할 때 기본적으로 UPN 클레임을 받습니다. 아래에 전체 프로세스를 보여 주는 다이어그램이 나와 있습니다.

SQL Server 구성

먼저 SQL Server 관련 작업부터 시작하겠습니다. 이 시나리오에서 SQL Server는 "SQL2"라는 서버에서 실행됩니다. SQL 서비스 자체는 네트워크 서비스로 실행됩니다. 즉, SQL 서비스에 대해 SPN을 만들지 않아도 됩니다. 서비스가 도메인 계정으로 실행된다면 MSSQLSvc에 대해 해당 서비스 계정용으로 SPN을 만들어야 합니다. 이 시나리오에서는 이전 Northwinds 데이터베이스를 사용하여 데이터를 검색합니다. 요청을 하는 사용자의 ID를 쉽게 표시하기 위해 Ten Most Expensive Products 저장 프로시저를 다음과 같이 수정했습니다.

 

CREATE procedure [dbo].[TenProductsAndUser] AS

SET ROWCOUNT 10

SELECT Products.ProductName AS TenMostExpensiveProducts, Products.UnitPrice, SYSTEM_USER As CurrentUser

FROM Products

ORDER BY Products.UnitPrice DESC

 

여기서 파악해야 할 중요한 사항은 열의 현재 사용자를 반환하기 위해 SYSTEM_USER를 SELECT 문에 추가했다는 것입니다. 즉, 쿼리를 실행하여 결과가 반환되면 현재 사용자 이름이 포함된 표의 열이 표시되므로 쿼리가 현재 사용자의 ID로 실행되었는지 여부를 쉽게 확인할 수 있습니다. 이 시나리오에서는 이 저장 프로시저를 실행하기 위한 세 가지 Windows 사용자 권한을 부여했으며, 다른 사용자는 저장 프로시저를 실행할 수 없습니다. 따라서 최종 출력도 쉽게 확인할 수 있습니다.

WCF 서비스 응용 프로그램 만들기

다음으로는 SQL에서 데이터를 검색하는 WCF 서비스 응용 프로그램을 만들었습니다. 앞서 CASI Kit 게시물 2부(https://blogs.msdn.com/b/sharepoint_ko/archive/2010/12/17/azure-sharepoint-2.aspx)에서 설명한 지침에 따라 SharePoint 팜과 WCF 응용 프로그램 간에 트러스트를 설정합니다. 이렇게 해야 요청을 하는 사용자의 클레임을 가져올 수 있습니다. 예를 들어 UPN 클레임 값을 단순히 매개 변수로 전달해서는 안 됩니다. 그러면 누구나 다른 UPN 클레임 값을 전달하여 다른 사람의 ID를 스푸핑할 수 있기 때문입니다. WCF와 SharePoint 간에 트러스트가 올바르게 구성되면 다음을 수행하는 메서드를 작성할 수 있습니다.

  • UPN 클레임 추출
  • c2wts를 통해 사용자 가장
  • 해당 사용자로 SQL에서 데이터 검색

 

이러한 작업에 사용한 코드는 다음과 같습니다.

 

//the following added for this code sample:

using Microsoft.IdentityModel;

using Microsoft.IdentityModel.Claims;

using System.Data;

using System.Data.SqlClient;

using System.Security.Principal;

using Microsoft.IdentityModel.WindowsTokenService;

using System.ServiceModel.Security;

 

 

public DataSet GetProducts()

{

 

   DataSet ds = null;

 

   try

   {

       string conStr = "Data Source=SQL2;Initial Catalog=

       Northwind;Integrated Security=True;";

 

       //ask for the current claims identity

       IClaimsIdentity ci =

          System.Threading.Thread.CurrentPrincipal.Identity as IClaimsIdentity;

 

       //make sure the request had a claims identity attached to it

       if (ci != null)

       {

          //see if there are claims present before running through this

          if (ci.Claims.Count > 0)

          {

              //look for the UPN claim

              var eClaim = from Microsoft.IdentityModel.Claims.Claim c in ci.Claims

              where c.ClaimType == System.IdentityModel.Claims.ClaimTypes.Upn

              select c;

 

              //if we got a match, then get the value for login

              if (eClaim.Count() > 0)

              {

                 //get the upn claim value

                 string upn = eClaim.First().Value;

 

                 //create the WindowsIdentity for impersonation

                 WindowsIdentity wid = null;

 

                 try

                 {

                     wid = S4UClient.UpnLogon(upn);

                 }

                 catch (SecurityAccessDeniedException adEx)

                 {

                           Debug.WriteLine("Could not map the upn claim to " +

                     "a valid windows identity: " + adEx.Message);

                 }

 

                 //see if we were able to successfully login

                 if (wid != null)

                 {

                        using (WindowsImpersonationContext ctx = wid.Impersonate())

                    {

                       //request the data from SQL Server

                        using (SqlConnection cn = new SqlConnection(conStr))

                        {

                           ds = new DataSet();

                           SqlDataAdapter da =

                               new SqlDataAdapter("TenProductsAndUser", cn);

                           da.SelectCommand.CommandType =

                               CommandType.StoredProcedure;

                           da.Fill(ds);

                        }

                     }

                 }

              }

          }

       }

   }

   catch (Exception ex)

   {

       Debug.WriteLine(ex.Message);

   }

 

   return ds;

}

 

위의 코드는 크게 복잡하지 않으므로 코드를 통해 수행하는 작업을 간략하게 설명하겠습니다. 먼저, 유효한 클레임 ID 컨텍스트가 있는지 확인하고 컨텍스트가 있는 경우 클레임 목록을 쿼리해 UPN 클레임을 찾습니다. UPN 클레임을 찾으면 클레임에서 값을 추출한 후에 c2wts를 호출하여 해당 사용자로 S4U 로그인을 수행하도록 합니다. 정상적으로 로그인되면 WindowsIdentity가 반환됩니다. 그러면 이 WindowsIdentity를 사용하여 가장 컨텍스트를 만듭니다. 사용자를 가장한 후에는 SQL Server에 연결하여 데이터를 검색합니다. 다음은 이 과정에서 사용 가능한 몇 가지 문제 해결 팁입니다.

  1. 응용 프로그램 풀에서 사용할 수 있도록 c2wts를 구성하지 않은 경우에는 외부 catch 블록에서 트래핑된 오류가 발생합니다. 즉, "WTS0003: 호출자에게 서비스에 액세스할 권한이 없습니다."와 같은 오류가 표시됩니다. c2wts를 구성하는 자세한 방법과 관련 링크는 이 게시물 뒷부분에 있습니다.
  2. Kerberos 제한 위임을 올바르게 설정하지 않은 경우 da.Fill(ds) 코드 줄을 사용하여 저장 프로시저를 실행하려고 하면 익명 사용자에게는 이 저장 프로시저를 실행할 권한이 없다는 예외가 throw됩니다. 이 시나리오용으로 제한된 위임을 구성하는 몇 가지 팁도 이 게시물 뒷부분에서 소개합니다.

C2WTS 구성

c2wts는 기본적으로 a) 수동으로 시작되며 b) 누구도 사용하지 못하도록 구성됩니다. 이 시나리오에서는 a) 자동으로 시작되고 b) WCF 서비스 응용 프로그램의 응용 프로그램 풀이 사용할 수 있도록 c2wts를 수정했습니다. 여기서는 이 권한 부여를 구성하는 방법은 자세히 설명하지 않으며, 관련 내용은 아래 문서에서 확인하실 수 있습니다. 구성 정보는 문서 끝부분에 나와 있습니다. https://msdn.microsoft.com/ko-kr/library/ee517258.aspx 그 외에는 특별히 수행해야 할 작업이 없습니다. c2wts의 자세한 배경 정보는 다음 문서에서도 확인할 수 있습니다. https://msdn.microsoft.com/ko-kr/library/ee517278.aspx

 

참고: 두 번째로 소개한 문서에는 큰 오류가 있는데요. 이 문서에서는 sc config c2wts depend=cryptosvc 코드를 실행하여 c2wts에 대한 종속성을 만들라고 되어 있습니다. 그러나 이렇게 해서는 안 됩니다. 이 코드는 철자가 잘못되었으며, “cryptosvc”는 올바른 서비스 이름이 아닙니다(적어도 Windows Server 2008 R2의 경우에는요). 이 코드를 실행하면 c2wts가 더 이상 시작되지 않고 종속성이 삭제되도록 표시되어 있거나 종속성을 찾을 수 없다는 메시지가 표시됩니다. 저도 이 코드를 실행했다가 이러한 상황을 겪었으며, 제 경우에는 c2wts를 사용하려면 최소한 WCF 호스트는 실행되어야 했기 때문에 종속성을 iisadmin으로 변경할 수밖에 없었습니다.

Kerberos 제한 위임 구성

제한 위임 구성이 매우 복잡하다고 생각하는 분들이 많으실 텐데요, 아래 두 가지 사항을 참고해 주시기 바랍니다.

  1. Kerberos 제한 위임의 작동 방식에 대해서는 자세히 설명하지 않겠습니다. 이와 관련된 정보는 인터넷에서 쉽게 확인하실 수 있습니다.
  2. 제가 이 게시물을 작성하는 현재 시점에는 제한 위임이 정상적으로 작동합니다.

 

그러면 위임을 구성하기 위해 필요한 작업을 확인해 볼까요? 먼저, 앞에서도 언급했듯이 이 시나리오에서는 SQL Server가 네트워크 서비스로 실행되기 때문에 별도의 작업이 필요하지 않습니다. 둘째로, WCF 응용 프로그램 풀은 vbtoys\portal이라는 도메인 계정으로 실행되므로 다음과 같은 두 가지 작업을 수행해야 합니다.

  1. 위임 원본 서버의 정규화된 이름과 NetBIOS 이름을 모두 사용하여 도메인 계정에 대한 HTTP SPN을 만듭니다. 여기서는 WCF 서버가 AZ1이므로 다음과 같은 두 SPN을 만듭니다.
    1. setspn -A HTTP/az1 vbtoys\portal
    2. setspn -A HTTP/az1.vbtoys.com vbtoys\portal
  2. "SQL2” 서버에서 실행되는 SQL Server 서비스에 대한 Kerberos 제한 위임에서 계정을 트러스트하도록 구성해야 합니다. 이렇게 하려면 도메인 컨트롤러에서 Active Directory 사용자 및 컴퓨터를 연 다음, vbtoys\portal 사용자를 두 번 클릭하고 위임(Delegation) 탭을 클릭하여 이 트러스트를 구성합니다. 모든 종류의 인증 프로토콜을 사용하여 특정 서비스에 대한 위임만 트러스트하도록 설정합니다. 아래 그림에 해당 위임 구성의 자세한 옵션이 나와 있습니다(클릭하면 큰 그림이 표시됨).

 

세 번째 작업은 제한된 위임에서 트러스트하도록 WCF 응용 프로그램 서버를 구성하는 것입니다. 다행히도 이 프로세스는 앞서 사용자의 경우에 설명한 것과 정확히 일치합니다. Active Directory 사용자 및 컴퓨터에서 컴퓨터 계정을 찾은 다음 구성하면 됩니다. 아래 그림에 해당 위임 구성의 상세한 옵션이 나와 있습니다(클릭하면 큰 그림이 표시됨).

 

 

이제 SharePoint와 관련이 없는 모든 사항은 설정 및 구성되었으며 실행할 준비가 되었습니다. 마지막으로는 구성을 테스트할 웹 파트를 만듭니다.

SharePoint 웹 파트 만들기

웹 파트 만들기는 간단합니다. 이전에 설명한 패턴에 따라 SharePoint에 대해 WCF 호출을 수행하고 현재 사용자의 ID를 전달하면 됩니다(https://blogs.technet.com/b/speschka/archive/2010/09/08/calling-a-claims-aware-wcf-service-from-a-sharepoint-2010-claims-site.aspx(영문일 수 있음)). CASI Kit를 사용하여 연결을 설정하고 WCF를 호출할 수도 있지만, 작업 방식을 쉽게 보여 드리기 위해 수동으로 진행하기로 했습니다. 웹 파트를 만드는 기본적인 단계는 다음과 같습니다.

  1. Visual Studio 2010에서 새 SharePoint 2010 프로젝트를 만듭니다.
  2. WCF 서비스 응용 프로그램에 대한 서비스 참조를 만듭니다.
  3. 새 웹 파트를 추가합니다.
  4. WCF에서 데이터를 검색하여 표에 표시하는 코드를 웹 파트에 추가합니다.
  5. 웹 파트를 호스팅할 웹 응용 프로그램의 web.config 파일 <system.ServiceModel> 섹션에 Visual Studio 프로젝트에서 생성된 app.config의 모든 정보를 추가합니다.

참고: app.config에는 decompressionEnabled라는 특성이 있는데, web.config 파일에 정보를 추가하기 전에 이 특성을 삭제해야 합니다. 이 특성을 그대로 두면 서비스 참조 프록시 인스턴스를 만들 때 웹 파트에서 오류가 발생합니다.

위에서 설명한 단계는 4단계를 제외하고는 간단하기 때문에 자세하게 설명하지 않습니다. 웹 파트의 코드는 다음과 같습니다.

private DataGrid dataGrd = null;

private Label statusLbl = null;

 

 

protected override void CreateChildControls()

{

   try

   {

       //create the connection to the WCF and try retrieving the data

       SqlDataSvc.SqlDataClient sqlDC = new SqlDataSvc.SqlDataClient();

 

       //configure the channel so we can call it with FederatedClientCredentials

       SPChannelFactoryOperations.ConfigureCredentials<SqlDataSvc.ISqlData>(

       sqlDC.ChannelFactory, Microsoft.SharePoint.SPServiceAuthenticationMode.Claims);

 

       //create the endpoint to connect to

       EndpointAddress svcEndPt =

          new EndpointAddress("https://az1.vbtoys.com/ClaimsToSqlWCF/SqlData.svc");

 

       //create a channel to the WCF endpoint using the

       //token and claims of the current user

       SqlDataSvc.ISqlData sqlData =

          SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

          <SqlDataSvc.ISqlData>(sqlDC.ChannelFactory, svcEndPt);

 

       //request the data

       DataSet ds = sqlData.GetProducts();

 

       if ((ds == null) || (ds.Tables.Count == 0))

       {

          statusLbl = new Label();

          statusLbl.Text = "No data was returned at " + DateTime.Now.ToString();

          statusLbl.ForeColor = System.Drawing.Color.Red;

          this.Controls.Add(statusLbl);

       }

       else

       {

          dataGrd = new DataGrid();

          dataGrd.AutoGenerateColumns = true;

          dataGrd.DataSource = ds.Tables[0];

          dataGrd.DataBind();

          this.Controls.Add(dataGrd);

       }

   }

   catch (Exception ex)

   {

       Debug.WriteLine(ex.Message);

   }

}

 

이 코드 역시 간단합니다. 첫 부분에서는 현재 사용자의 클레임을 전달하는 방식으로 WCF 서비스에 대한 연결을 설정합니다. 자세한 내용은 위에 나와 있는 이 주제에 대한 이전 블로그 게시물 링크를 참조하십시오. 나머지 코드 부분은 데이터 집합을 다시 가져와서 데이터가 있으면 표에 바인딩하거나, 데이터를 가져오지 못하는 경우 데이터가 없다는 레이블을 표시합니다. 이러한 모든 코드가 함께 작동하는 방식을 보여 주는 스크린샷 3개가 아래에 나와 있습니다. 처음 두 스크린샷에서는 서로 다른 두 사용자(CurrentUser 열에서 확인 가능)에 대해 코드가 작동하는 방식을 보여 주며, 세 번째 스크린샷에서는 저장 프로시저 실행 권한이 부여되지 않은 사용자에 대해 코드가 작동하는 방식을 보여 줍니다.

 

 

이것으로 게시물을 마무리하겠습니다. WCF 서비스 응용 프로그램과 웹 파트의 코드가 아래에 첨부되어 있습니다. 이 게시물의 서식으로 인해 코드가 잘못 표시될 가능성이 있으므로 이 코드를 작성한 원본 Word 문서도 함께 첨부합니다.

이 문서는 번역된 블로그 게시물입니다. 원본 문서는 Using SAML Claims, SharePoint, WCF, Claims to Windows Token Service and Constrained Delegation to Access SQL Server를 참조하십시오.