Использование утверждений SAML, SharePoint, WCF, утверждений службы токенов Windows и ограниченного делегирования для доступа к SQL Server

Дата публикации исходной статьи: воскресенье, 07 августа 2011 г.

Привет. Думаю, это самая длинная статья, какую мне когда-либо приходилось писать, однако в ней я постарался охватить все доступные технологии. Насколько мне известно, эта тема активно обсуждалась в последнее время. Весь вопрос заключается в том, может ли пользователь утверждений SAML использовать контекст Windows для доступа к другим приложениям. В SharePoint 2010 реализована ограниченная поддержка утверждений службы токенов Windows (далее в этой статье такие утверждения обозначаются аббревиатурой c2wts) только для пользователей утверждений Windows с небольшим числом приложений-служб. Чаще всего возникает вопрос, почему не поддерживаются пользователи SAML с действительными утверждениями имени участника-пользователя, в то время как никаких технологических препятствий для этого не существует. Поэтому в условиях ограничения типов проверки подлинности и приложений-служб, которые могут их использовать, вы можете столкнуться с ситуацией, когда требуется обеспечить подключение пользователей SAML к приложениям, отличным от их базовой учетной записи Windows. В этой публикации я постараюсь в общем виде продемонстрировать, как можно сделать это.

Основной подход в этом сценарии заключается в создании приложения служб WCF, которое будет обрабатывать все запросы конечных пользователей на получение данных другого приложения (в нашем случае — SQL Server). Итак, нам нужно получить данные от пользователя SAML, обращающегося к сайту SharePoint, и выполнить запрос от имени соответствующей ему учетной записи Windows для извлечения данных из SQL Server. ПРИМЕЧАНИЕ. Эта статья посвящена пользователям утверждений SAML, однако описываемая в ней методика применяется и к пользователям утверждений Windows, которые по умолчанию получают утверждение имени участника-пользователя при входе в систему. На следующем рисунке показана схема этого процесса:

Настройка SQL Server

Для начала рассмотрим процесс на стороне SQL Server. В этом сценарии SQL Server работает на сервере "SQL2". Служба SQL работает как сетевая служба. Это означает, что мне не нужно создавать для нее имя участника-службы, что потребовалось бы при работе этой службы под учетной записью домена для MSSQLSvc. В этом сценарии я буду извлекать данные из хорошо знакомой учебной базы данных "Борей" (Northwind). Мне нужен удобный доступ к удостоверению пользователя, выполняющего запрос, поэтому я изменил хранимую процедуру TenMostExpensiveProducts следующим образом:

 

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

 

Здесь следует обратить внимание, что в оператор SELECT добавлен объект SYSTEM_USER, который возвращает текущего пользователя в столбце. Это означает, что после выполнения запроса и получения результатов в таблицу будет добавлен столбец с именем текущего пользователя, с помощью которого я могу определить, выполнялся ли запрос с использованием удостоверения текущего пользователя или нет. В этом сценарии трем пользователям Windows предоставлены права на выполнение этой хранимой процедуры. Другим пользователям она будет недоступна (также полезный пример ограничения прав).

Создание приложения служб WCF

Далее создадим приложение служб WCF, которое извлекает данные из SQL. При этом я буду придерживаться рекомендаций, приведенных в части 2 публикации, посвященной набору средств CASI 2 (http://blogs.msdn.com/b/sharepoint_ru/archive/2010/12/16/azure-sharepoint-2.aspx); Это позволяет установить отношение доверия между фермой SharePoint и приложением WCF. Это необходимо, чтобы получать утверждения пользователя, выполняющего запрос. Нам необходимо предотвратить передачу значения утверждения имени участника-пользователя в качестве параметра, поскольку в этом случае злоумышленник может подделать удостоверение любого пользователя, передав другое значение утверждения. После настройки доверия между приложением WCF и фермой SharePoint можно создать метод, реализующий следующие возможности:

  • Извлечение утверждения имени участника-пользователя
  • Олицетворение пользователя с помощью утверждений c2wts
  • Извлечение данных из SQL от имени этого пользователя

 

Ниже приводится код метода:

 

//для этого примера кода добавлено следующее:

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

 

       //запрос текущих удостоверений утверждений

       IClaimsIdentity ci =

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

 

       //убедитесь, что к запросу прикреплено удостоверение утверждения

       if (ci != null)

       {

          //посмотрите, присутствуют ли утверждения, прежде чем выполнять это

          if (ci.Claims.Count > 0)

          {

              //ищите утверждение имени участника-пользователя

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

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

              select c;

 

              //если мы получили соответствие, тогда получим значение для входа

              if (eClaim.Count() > 0)

              {

                 //получение значения утверждения имени участника-пользователя

                 string upn = eClaim.First().Value;

 

                 //создание WindowsIdentity для олицетворения

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

                 }

 

                 //посмотрите, смогли ли мы успешно выполнить вход

                 if (wid != null)

                 {

                        using (WindowsImpersonationContext ctx = wid.Impersonate())

                    {

                       //запрос данных из 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;

}

 

Этот код достаточно прост, поэтому я лишь вкратце опишу принцип его работы. Сначала я проверяю наличие допустимого контекста удостоверений утверждений и в случае успеха запрашиваю список утверждений в поисках утверждения имени участника-пользователя. Предполагая, что найдено утверждение имени участника-пользователя, я извлекаю его значение и выполняю вызов c2wts для входа в систему S4U от его имени. В случае успешного входа возвращается удостоверение WindowsIdentity, на основе которого создается контекст олицетворения. После олицетворения пользователя я создаю собственное подключение к SQL Server и извлекаю данные. Далее приведу пару рекомендаций по устранению неполадок:

  1. Если у вас не разрешено использование утверждения c2wts пулом приложений, возникает следующая ошибка, перехватываемая во внешнем блоке catch: "WTS0003: у вызвавшего процесса нет прав на доступ к службе". Ниже приводятся рекомендации по настройке c2wts и ссылка на дополнительные сведения.
  2. Если неправильно настроено ограниченное делегирование Kerberos, при попытке выполнить хранимую процедуру со строкой кода da.Fill(ds); возникнет исключение, свидетельствующее об отсутствии у анонимного пользователя прав на ее выполнение. Здесь я также дам пару советов по настройке ограниченного делегирования в этом сценарии.

Настройка C2WTS

В соответствии с настройками утверждения c2wts по умолчанию оно запускается вручную и недоступно пользователям. Я настроил его на автоматический запуск и использование пулом приложений для моего приложения служб WCF. Здесь я не буду подробно описывать процесс настройки авторизации. Все необходимые сведения приводятся в конце следующей статьи: http://msdn.microsoft.com/en-us/library/ee517258.aspx. Вот и все, что нужно сделать. Дополнительные сведения о c2wts см. в следующей статье http://msdn.microsoft.com/en-us/library/ee517278.aspx.

 

Примечание. В этой статье есть лишь одна СЕРЬЕЗНАЯ ошибка — в ней рекомендуется создать зависимость для c2wts с помощью следующего кода: sc config c2wts depend=cryptosvc. НЕ ДЕЛАЙТЕ ТАК! В имя службы "cryptosvc" закралась опечатка. По крайней мере, в Windows Server 2008 R2 такой службы нет. Если сделать так, утверждение c2wts не будет запускаться, поскольку зависимость отмечена для удаления и не может быть обнаружена. Я сам столкнулся с этой проблемой и, чтобы исправить ее, изменил зависимость на iisadmin (в моем случае это логично, поскольку для использования c2wts как минимум должен работать мой узел WCF).

Настройка ограниченного делегирования Kerberos

Итак, пока вы окончательно не запутались, я поясню:

  1. В этой статье я не планирую погружаться в дебри настройки ограниченного делегирования Kerberos, поскольку этому вопросу уже посвящены сотни научных трудов.
  2. На момент написания мой код работал без проблем.

 

Итак, рассмотрим, что нам нужно для настройки делегирования. Во-первых, как я уже упоминал ранее, служба SQL Server выполняется как сетевая служба и не требует дополнительной настройки. Во-вторых, пул приложений WCF работает под учетной записью домена с именем vbtoys\portal. Для него требуется выполнить следующие действия:

  1. Создадим для него имя участника-службы HTTP с использованием NetBIOS-имени и полного имени сервера, с которого осуществляется делегирование. В моем сценарии сервер WCF носит имя AZ1, поэтому было создано два следующих имени участника-службы:
    1. setspn -A HTTP/az1 vbtoys\portal
    2. setspn -A HTTP/az1.vbtoys.com vbtoys\portal
  2. Далее необходимо настроить мою учетную запись как надежную для ограниченного делегирования Kerberos службам SQL Server, выполняющимся на сервере "SQL2". Для этого следует перейти в контроллер домена и открыть оснастку "Active Directory - пользователи и компьютеры". Дважды щелкнем пользователя vbtoys\portal, откроем вкладку "Делегирование" и настроим нужное отношение доверия. Я настраиваю доверенное делегирование отдельным службам с использованием любого протокола проверки подлинности. По следующей ссылке вы можете ознакомиться с примером такой конфигурации делегирования:

 

В-третьих, мне необходимо настроить сервер приложений WCF как надежный для ограниченного делегирования. К счастью, этот процесс полностью идентичен описываемому выше процессу для пользователя. В оснастке "Active Directory - пользователи и компьютеры" необходимо выбрать и настроить учетную запись компьютера. Для просмотра конфигурации, воспользуйтесь следующей ссылкой:

 

 

После этого все компоненты, не относящиеся к SharePoint, установлены, настроены и готовы к работе. Осталось создать веб-часть для проверки.

Создание веб-части SharePoint

Процесс создания веб-части достаточно прост. Я использую ранее описанную процедуру выполнения вызовов WCF к SharePoint и передачи удостоверения текущего пользователя (http://blogs.technet.com/b/speschka/archive/2010/09/08/calling-a-claims-aware-wcf-service-from-a-sharepoint-2010-claims-site.aspx). Также можно установить подключение и выполнять вызовы WCF с помощью набора средств CASI, однако я решил реализовать этот процесс вручную. Далее описываются основные действия по созданию веб-части:

  1. Создайте новый проект SharePoint 2010 в Visual Studio 2010.
  2. Создайте ссылку на приложение служб WCF.
  3. Добавьте новую веб-часть.
  4. Добавьте в веб-часть код, позволяющий извлекать данные из WCF и отображать их в таблице.
  5. Добавьте все данные из файла app.config, созданного в проекте Visual Studio, в раздел <system.ServiceModel> файла web.config для веб-приложения, в котором будет размещаться создаваемая веб-часть.

Примечание. В файле app.config присутствует атрибут decompressionEnabled; ЕГО НЕОБХОДИМО УДАЛИТЬ ПЕРЕД ДОБАВЛЕНИЕМ В ФАЙЛ WEB.CONFIG. Если оставить этот атрибут в веб-части, при попытке создать экземпляр прокси ссылки на службу произойдет ошибка.

Описываемые выше действия, за исключением 4-го, достаточно очевидны, поэтому я не буду рассматривать их подробно. Ниже приводится код создаваемой веб-части:

private DataGrid dataGrd = null;

private Label statusLbl = null;

 

 

protected override void CreateChildControls()

{

   try

   {

       //создание подключения к WCF и попытка извлечения данных

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

 

       //настройка канала, чтобы мы могли вызывать его с помощью FederatedClientCredentials

       SPChannelFactoryOperations.ConfigureCredentials<SqlDataSvc.ISqlData>(

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

 

       //создание конечной точки для подключения

       EndpointAddress svcEndPt =

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

 

       //создание канала к конечной точке WCF с использованием

       //токена и утверждений текущего пользователя

       SqlDataSvc.ISqlData sqlData =

          SPChannelFactoryOperations.CreateChannelActingAsLoggedOnUser

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

 

       //запрос данных

       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 для передачи утверждения текущего пользователя (подробнее см. в приведенных ссылках на мои предыдущие записи блога по этой тематике). Далее просто выполняется получение набора данных и его привязка к таблице (при наличии данных) или вывод сообщения (при отсутствии данных). На следующих трех снимках экрана демонстрируется совместная работа этих частей (на первых двух рисунках — для двух разных пользователей, что можно увидеть в столбце CurrentUser). На третьем показан сценарий для пользователя, у которого нет прав на выполнение хранимой процедуры.

 

 

На этом, вроде, все. Я включил в эту статью код для приложения служб WCF и веб-части, а также исходный документ Word с текстом статьи, поскольку форматирование этих публикаций, как всегда, оставляет желать лучшего.

Это локализованная запись блога. Исходная статья находится по адресу Using SAML Claims, SharePoint, WCF, Claims to Windows Token Service and Constrained Delegation to Access SQL Server