Re[2]: Вариант №1 "Чистый" WCF
От: WSA  
Дата: 30.01.14 07:58
Оценка:
Михаил, здравствуйте.

Кажется получается.

2 Сервиса, натсроенные с той же конфигурацией, что приведена в вашем описании вполне себе аутентифицируют клиента по переданному токену, без указания пароля.
Обращаюсь из клиента примерно так:


public void RequestSecuredWCFOne(GenericXmlSecurityToken token){
            using (var cf = new ChannelFactory<IService2>("WS2007FederationHttpBinding_IService2"))
            {
                cf.Credentials.UseIdentityConfiguration = true;
                var proxy = cf.CreateChannelWithIssuedToken(token);

                var data = proxy.GetData(1);
                Console.WriteLine(data);

                ((IDisposable)proxy).Dispose();
            }
        }



Токен получается заранее на клиенте прямо с STS.
Я пока не знаю как поведёт себя эта схема если будут разные сертификаты или адреса RP... тут много вариантов. Сейчас займусь этим исследованием. Но самое главное, что есть сильная подвижка.
Благодарю Вас за помощь. Но всё равно с нетерпением буду ждать второй части статьи. У вас здорово получается, уверен, можно будет многое из неё почерпнуть!

С уважением,
Сергей
Re[3]: Вариант №1 "Чистый" WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 30.01.14 11:03
Оценка:
Здравствуйте, WSA, Вы писали:

WSA>Кажется получается.


Все, верно.
Просто у этого варианта нужно понимать, что:
1. В идеале, разным сервисам нужен разный токен.
Это не обязательное требование. По большому счету, чтобы воспользоваться токеном сервис должен уметь его расшифровать (т.е. у него должен быть сертификат с закрытым ключом) и проверить подпись STS. Еще WIF позволяет проверять кому назначен токен (там указывается URL сервиса), но эту проверку можно и отключить (<audienceUris mode="Never" />)
Когда все сервисы принадлежат вам, можно просто сделать везде единые настройки и забыть об этом.

Схема начинает плыть, например, когда сервисы, скоторыми вам надо общаться, принадлежат другим компаниям.
В этом случае врятли кто-то согласится использовать единый закрытый ключ, да и в принципе — claims которые получает каждый сервис, могут существенно различаться.

2. Токен имеет ограничение по времени использования и время от времени его надо обновлять.

3. Смотря конечно для чего использовать токены...
Но вообще — это не только механизм аутентификации, но и способ для передачи разной полезной информации о пользователе: права, роли, личные данные (ФИО, должность, email, ...) которая хранится и обновляется централизованно. Поэтому логично хотябы часть этой информации получать, например, на клиенте.
Re[3]: Вариант №1 "Чистый" WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 30.01.14 11:06
Оценка:
Здравствуйте, WSA, Вы писали:

WSA>Но всё равно с нетерпением буду ждать второй части статьи. У вас здорово получается, уверен, можно будет многое из неё почерпнуть!

Да не так уж и много — вы уже почти все нашли сами.
Re: Re: Вариант №2 "Ручное" получение токена
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 02.02.14 09:25
Оценка: 98 (3)
Сергей, добрый день

Прошу прощения, что приходится писать вот так "в неделю по чайной ложке" — увы, быстрее не получается: нужно все вспомнить и проверить + разобраться с особенностями Thinktecture IdentityServer (мы сами используем самописное решение) + выбрать время для собственно статьи.

К счастью, как я вижу, у вас и так все движется не плохо. Однако, раз уж обещал — закончу всю серию (надеюсь, пойдет уже шибче).

Итак, в первом варианте мы все переложили на WCF и только передавали ему credentials. В этот раз мы по сути полностью повторим весь процесс работы с токенами, который реализует WCF, но сделаем это "вручную": будем получать сертификат явно, а затем явно же передавать его сервисам.

Собственно, у нас уже почти все для этого готово, нужно только указать явно 1 endpoint в настройках клиента — по нему мы будем обращаться к STS.

Я все же немного перепишу app.confug для клиента (удалю все лишнее):
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <bindings>
      <ws2007FederationHttpBinding>
        <binding name="Service">
          <!--    Здесь мы не указываем параметров для Issuer, т.к. обращаться будем сами явно 
                однако, если оставить просто такие настройки, WCF начнет показывать окно выбора токена
                чтобы это предотвратить нужно или указать специальный параметр в <endpointBehaivor>
                или аналогичный параметр в коде (мы сделаем так)
          -->
          <security mode="TransportWithMessageCredential" />
        </binding>
      </ws2007FederationHttpBinding>
      <ws2007HttpBinding>
        <binding name="ForIssue">
          <security mode="TransportWithMessageCredential">
            <message clientCredentialType="UserName" establishSecurityContext="false" />
          </security>
        </binding>
      </ws2007HttpBinding>
    </bindings>
    <client>
      <endpoint address="https://мой_WCF_Service/WcfService/Service.svc"
          binding="ws2007FederationHttpBinding" bindingConfiguration="Service"
          contract="ServiceReference.Service" name="Service" />
      <!-- Мы добавляем новый endpoint -->
      <endpoint name="STS"
                address="https://мой_STS/IdSrv/issue/wstrust/mixed/username"
                binding="ws2007HttpBinding" bindingConfiguration="ForIssue"
                contract="System.ServiceModel.Security.IWSTrustChannelContract" />
    </client>
  </system.serviceModel>
</configuration>


Сам код клиента довольно прост:
// Формируем канал до STS - именно здесь мы указываем credentials пользователя
var stsChannelFactory = new WSTrustChannelFactory("STS");
stsChannelFactory.Credentials.UserName.UserName = "User1";
stsChannelFactory.Credentials.UserName.Password = "123456";
var stsClient = stsChannelFactory.CreateChannel();

// Запрашиваем токен
var token = stsClient.Issue(
    new RequestSecurityToken(RequestTypes.Issue)
    {
        // Указываем для какого RP нам нужен токен
        AppliesTo = new EndpointReference("https://мой_WCF_Service/WcfService/Service.svc")
    });

// Теперь канал уже до самого сервиса
var wcfServiceFactory = new ChannelFactory<ServiceReference.Service>("Service");
wcfServiceFactory.Credentials.UseIdentityConfiguration = true;
// Подавляем показ окошка выбора токена
wcfServiceFactory.Credentials.SupportInteractive = false;

// Собственно получение канала, у которого в качестве Credentials указан полученный ранее токен.
var channel = wcfServiceFactory.CreateChannelWithIssuedToken(token);
Console.WriteLine(channel.GetData());


Все довольно просто, однако, как обычно, есть несколько подводных камней.

1. Можно ли теперь забыть credentials пользователя и хранить в клиенте только токен?
Вопрос, как мне кажется, очень спорный. Вот мои соображения...
Токен — это тоже своеобразная форма credentials. Ведь по сути как работает токен (часто для аналогии приводят выдачу, например, водительских прав):

В чем же здесь подвох?
Во-первых, токен — это snapshot информации аутентифицирующего центра на момент вашего обращения к нему. А ситуация с момента выдачи может уже поменяться. Например, у пользователя поменялись права доступа или его вообще заблокировали в системе, а в токене все еще старая информация (это при случае, что права/роли вы тоже передаете в токене — иногда это реально удобно).
Во-вторых, токен, как и любые другие credentials может быть перехвачен. Это не значит, что токен плох! Это значит, хранение и передача токена ничуть не безопаснее хранения и передачи любого другого вида credentials.
Наконец, имеется, пусть и теоретическая, вероятность дискредитации сертификата, которым подписываются токены. Т.е. в какой-то момент мы уже не можем доверять токенам, подписанным сертификатом X, т.к. помимо нашего аутентификационного центра есть еще кто-то, кто может генерировать и подписывать токены.

Как с этим бороться?
Собственно, на сегодня используют простой подход — каждый токен имеет ограниченный срок действия. Какой конкретно — определяется администраторами системы. Я встречал токены сроком действия от нескольких минут до полумесяца. Срок действия указывается в самом токене, т.е. он тоже защищен подписью от подделки.
На самом деле, время действия это рекомендация для RP, которой они могут и не следовать и продолжать принимать просроченные токены. Но тогда они сами себе вредители.

2. Можно ли с одним токеном обращаться к разным RP?
Если вы посмотрите на структуру типичного SAML-токена, то увидите примерно следующее (нужная строчка выделена):
<Assertion ID="_311184a5-4c69-4c49-aa9f-a6b3bee96e32" IssueInstant="2014-02-02T08:16:37.209Z" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>STS</Issuer>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <!-- Тут информация о подписи STS - это не интересно -->
  </Signature>
  <Subject>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"></SubjectConfirmation>
  </Subject>
  <Conditions NotBefore="2014-02-02T08:16:36.933Z" NotOnOrAfter="2014-02-02T08:36:36.933Z">
    <AudienceRestriction>
      <Audience>https://мой_WCF_Service/WcfService/Service.svc</Audience>
    </AudienceRestriction>
  </Conditions>
  <AttributeStatement>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
      <AttributeValue>User1</AttributeValue>
    </Attribute>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
      <AttributeValue>user1@companydomain.com</AttributeValue>
    </Attribute>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/webpage">
      <AttributeValue>http://myhome.home</AttributeValue>
    </Attribute>
  </AttributeStatement>
  <AuthnStatement AuthnInstant="2014-02-02T08:16:36.450Z">
    <AuthnContext>
      <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
    </AuthnContext>
  </AuthnStatement>
</Assertion>

Т.е. привязка к конкретному RP идет всего по одному параметру.

Этот параметр можно игнорировать, указав в настройках WIF на стороне RP опцию <audienceUris mode="Never" /> (я о ней писал еще в первом варианте)

Помимо самого SAML, зависимость от RP может выражаться в том, какими ключами шифруется токен при передаче на RP. Здесь мы можем: или совсем отключить шифрование при передаче токена, или указать что для всех RP используется один набор ключей и шифровать им (ну и на каждый сервер RP установить нужный сертификат).

Это что касается технических ограничений, однако, есть еще и соображения предметного плана.
Вообще говоря, STS не обязан выдавать на все запросы одни и те же токены. В самом простом случае для каждого RP он может формировать собственный набор claims (например, условно говоря, ФИО и дату рождения вы будуту передавать только нескольким доверенным RP, а остальным — только логин). В этом случае все одназначно: набор токенов для каждого сервиса — свой.

3. Можно ли, раз уж мы получили токен на клиенте, воспользоваться информацией из него?
Об этом я напишу немного погодя, в отдельном сообщении — а это и так большим получилось
Re[2]: Вариант №2.1 Получение информации из токена на клиенте
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 02.02.14 10:36
Оценка: 3 (1)
Продолжим.

Еще раз продублирую вопрос:
3. Можно ли, раз уж мы получили токен на клиенте, воспользоваться информацией из него?

Можно, но "в лоб" это сделать не получится.
Дело в том, что настройки по умолчанию для Thinktecture IdentityServer (а точнее, для WIF), таковы, что все токены выданные по WS-Trust по умолчанию шифруются. Т.е. вместо узла с тоеном вы получите в ответе вот такой XML:
<EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" 
                      xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
    <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
    <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
      <!-- Информация о ключе шифрования  -->
    </KeyInfo>
    <xenc:CipherData>
      <xenc:CipherValue>OQFpRRqqa5z8Rkgfs ... e9ltVY=</xenc:CipherValue>
    </xenc:CipherData>
  </xenc:EncryptedData>
</EncryptedAssertion>


Какие есть варианты?

Я предлагаю пойти по 3-у варианту!

Для начала создадим отдельную RP для клиента. Из всех настроек нам нужен только URI (Realm), ну и, конечно, сертификата шифрования мы не указываем, т.к. шифровать и не планируем.


Будет ли этого достаточно? Увы нет. Если мы попытаемся запросить сертификат для этой RP — STS упадет с внутренней ошибкой, что не указаны Ecripting credetials (ну или что-то в этом ключе). Почему так происходит?
Для этого надо понять как STS от Thinktecture решает когда токен надо шифровать (на самом деле, они это поведение не меняли — оно тянется с самой WIF) и, соответсвенно, начинает искать сертификат шифрования, коий благополоучно не находит и валится. Упрощенный алгоритм выглядит так:

Чтобы решить первое, мы пойдем в настройке Thinktecture сервера и снимем галочку принудительного шифрования:


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

Думаю, тип ключей можно как-то задать и настройкой на STS, но мы пойдем по пути явного указания их в запросе токена.
Таким образом, наш запрос токена будет выглядить так:
var client = stsChannelFactory.CreateChannel();
var token = client.Issue(
    new RequestSecurityToken(RequestTypes.Issue)
    {
        AppliesTo = new EndpointReference("urn:Client"),
        KeyType = KeyTypes.Bearer
    });



Итак, теперь к нам пришел сертификат, правда лежит он там пока что в виде XML. Конечно, можно разобрать её руками, но, раз WIF все умеет и так, то сложим все на неё.
Нужно только иметь в виду, что не позволяет просто "прочитать токен", она может только "проверить валидность и прочитать токен", а для проверки валидности ей нужно указать каким сертификатом будем подписываться токен.

Для этого дополним наш app.config:
<configuration>
  <configSections>
    <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
  </configSections>
  
  <system.identityModel>
    <identityConfiguration name="ClientConf">
      <securityTokenHandlers>
        <securityTokenHandlerConfiguration>
          <audienceUris mode="Never" />
          <issuerNameRegistry>
            <trustedIssuers>
              <add name="STS" thumbprint="9B14A55B4AB96F5D7188C88D14779F57193905C2"/>
            </trustedIssuers>
          </issuerNameRegistry>
        </securityTokenHandlerConfiguration>
      </securityTokenHandlers>
    </identityConfiguration>
  </system.identityModel>
  
  <system.serviceModel>
   <!-- А здесь без изменений -->
  </system.serviceModel>
</configuration>


Полный пример на запрос и чтение токена выглядит так:
var stsChannelFactory = new WSTrustChannelFactory("STS");
stsChannelFactory.Credentials.UserName.UserName = "User1";
stsChannelFactory.Credentials.UserName.Password = "123456";

var client = stsChannelFactory.CreateChannel();
var token = client.Issue(
    new RequestSecurityToken(RequestTypes.Issue)
    {
        AppliesTo = new EndpointReference("urn:Client"),
        KeyType = KeyTypes.Bearer
    }
    );

var identityConfiguration = new IdentityConfiguration("ClientConf");
// Нужный нам SAML токен лежит в token.TokenXml (ну и берем от него OuterXml, т.к. будем все парсить)
var xmlReader = XmlReader.Create(new StringReader(((GenericXmlSecurityToken)token).TokenXml.OuterXml));

var samlToken = identityConfiguration.SecurityTokenHandlers.ReadToken(xmlReader);
var claimsCollection = identityConfiguration.SecurityTokenHandlers.ValidateToken(samlToken)[0];

// В принципе получать именно principal не обязательно, но т.к. он используется везде, то и здесь сделаем так же
var principal = new ClaimsPrincipal(new ClaimsIdentity(claimsCollection));


Ну и под занавес — можно ли этот токен передавать на RP?
В принципе, если мы не будем использовать шифрование на уровне сообщений (а будем на уровне транспорта), и не будем проверять какой именно RP предназначался токен — то почему-бы и нет.
Нужно только внести ряд небольших исправлений в настройки биндингом на сервере и клиенте (чтобы они стали допускать токены с BearerKey).

web.config на серевере RP (изменения полужирным):
<system.serviceModel>
    <bindings>
      <ws2007FederationHttpBinding>
        <binding name="Default">
                   <security mode="TransportWithMessageCredential">
            <message issuedKeyType="BearerKey">
              <issuerMetadata address="https://мой_STS/IdSrv/issue/wstrust/mex" />
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>
    </bindings>
 </system.identityModel>


Аналогично в app.config на клиенте (изменения полужирным)
<ws2007FederationHttpBinding>
  <binding name="Service">
    <security mode="TransportWithMessageCredential" >
      <message issuedKeyType="BearerKey"/>
    </security>
  </binding>
</ws2007FederationHttpBinding>


Все, теперь можно везде использовать оди и тот же токен.

Другой вопрос — на сколько это оправдано. Но тут уже к архитекторам конкретной системы.
Re[2]: Вариант №2 "Ручное" получение токена
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 02.02.14 10:46
Оценка: 3 (1)
Есть еще и 3-ий вариант, так называемое делегирование — когда вы получаете токен нужного пользователя на клиенте, но не передаете его явно на каждый RP, а используете для получения токенов от STS и уже с теми токенами образаетесь к RP.

Я не описал его потому что:

Я все же попытаюсь описать и этот вариант. Надеюсь, другие участники форума не будут против периодической активности в данной ветке..
Re[2]: Вариант №2 "Ручное" получение токена
От: WSA  
Дата: 03.02.14 07:35
Оценка:
Здравствуйте Михаил.

Вы снова порадовали дельной статьёй!
Если позволите, мои комментарии..

> В чем же здесь подвох?

> Во-первых, токен — это snapshot информации аутентифицирующего центра на момент вашего обращения к нему. А ситуация с момента выдачи может уже поменяться. Например, у пользователя поменялись права доступа или его вообще заблокировали в системе, а в токене все еще старая информация (это при случае, что права/роли вы тоже передаете в токене — иногда это реально удобно).
Что касается внезапного запрета пользователю входа. Нверное или полагаться на относительно небольшой срок действия токена, или реализовывать синхронизацию(но это серьёзный подход, который не всегда оправдан, если не критично)
Синхронизацию изменения клеймов в пользу пользователя, реальнее всего решить рекомеднацией пользователю, совершить выход и повторную аутентификацию. При этом(выходе) старый токен будет удалён и затребован новый.


> Во-вторых, токен, как и любые другие credentials может быть перехвачен. Это не значит, что токен плох! Это значит, хранение и передача токена ничуть не безопаснее хранения и передачи любого другого вида credentials.

Вот именно, только токен тут отличает, что его полезность ограничена по времени в отличии от пароля (что вы заметили далее в статье).

> вероятность дискредитации сертификата, которым подписываются токены

Это да, Но со временем можно усовершенствовать эту систему, через методы борьбы с "человек по середине"... Я пока не вникал в эту тему.

> Дело в том, что настройки по умолчанию для Thinktecture IdentityServer (а точнее, для WIF), таковы, что все токены выданные по WS-Trust по умолчанию шифруются. Т.е. вместо узла с тоеном вы получите в ответе вот такой

> Какие есть варианты?
А что если расшифровывать токен на клиенте, для целей клиента, но передавать на RP токен зашифрованный, который было получен от STS??


И вообще по поводу шифрования токенов. Я планирую шифровать токены. И код работает на моей машине, но я подозреваю, работает потому, что на моей машине установлены
закрытые ключи для сертификтов и клиент шифрует токен прежде чем отправить на RP и поэтому всё работает...


>Есть еще и 3-ий вариант, так называемое делегирование — когда вы получаете токен нужного пользователя на клиенте, но не передаете его явно на каждый RP, а используете для получения токенов от STS и уже с теми токенами образаетесь к RP.

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

В данный момент мне кажутся оптимальными 2 варианта. Какой из них удастся реализовать, такой и буду использовать.
1) Получение токена клиентом заранее. Передача на RP версию токена, которая не была расшифрована, чтобы небыло необходимости шифровать клиентом, читай иметь на клиенте закрытый ключ STS....
2) Получение Credentials в виде токена, и использование этих Credentials для доступа к остальным сервисам. Очень красивый вариант.
Re[3]: Вариант №2 "Ручное" получение токена
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 03.02.14 08:11
Оценка:
Здравствуйте, WSA, Вы писали:

WSA>Что касается внезапного запрета пользователю входа. Нверное или полагаться на относительно небольшой срок действия токена, или реализовывать синхронизацию(но это серьёзный подход, который не всегда оправдан, если не критично)

Ну или какой-то промежуточный вариант — типа оповещения всех сервисов о том, что токены до даты XXX более не действительны — тогда пользователю нужно будет явно перезапросить токен снова.

WSA>Синхронизацию изменения клеймов в пользу пользователя, реальнее всего решить рекомеднацией пользователю, совершить выход и повторную аутентификацию. При этом(выходе) старый токен будет удалён и затребован новый.

Да, но чисто в теории — если пользователю будут урезать права, ему выгоднее никогда не выходить из системы (или пока не кончится время жизни токена).

WSA>А что если расшифровывать токен на клиенте, для целей клиента, но передавать на RP токен зашифрованный, который было получен от STS??

На самом деле, а надо ли шифровать токены при передаче на RP? Если там нет критически важной информации, то может ну его?
Я в продолжении второй части статьи
Автор: Михаил Романов
Дата: 02.02.14
именно такой вариант показывал — токен без сессионных ключей уже не требует обязательного шифрования и, как следствие, мы можем его читать где угодно, а сама защита осуществляется с помощью защиты канала.

>>Есть еще и 3-ий вариант, так называемое делегирование — когда вы получаете токен нужного пользователя на клиенте, но не передаете его явно на каждый RP, а используете для получения токенов от STS и уже с теми токенами образаетесь к RP.

WSA>Вот этот кажется как раз именно мой. Он покрывает такие требования:
WSA>- На клиенте в памяти не зранится логин и пароль. Хранится ограниченый по времени токен.
WSA>- Все токены шифруются по умолчанию, без лишних приседаний
Хм... Ок, постараюсь выкроить время и таки добить этот вариант.
Но все же вопрос — а реально, нужно ли шифровать токены при передаче? Не достаочно ли просто получать их по защищенным каналам (SSL)?
Ведь чтобы работать с токеном для клиента, вам нужно будет сертификаты держать где-то поблизости от клиента (ставить в хранилище или зашивать в сборку/исполнимый файл, ...) — а значит проблем с доставанием этого сертификата у того, кто имеет доступ к клиенту — не будет.
Зато вам — лишние хлопоты.

WSA>2) Получение Credentials в виде токена, и использование этих Credentials для доступа к остальным сервисам. Очень красивый вариант.

Хорошо. Я не буду ничего обещать — но постараюсь-таки выкроить время и описать этот вариант (у меня остались некоторые сложности с самим Thinktecture — он не рассчитан на вариант делегирования без авторизации или с авторизацией от имени приложения, а не пользователя.
Т.е. в его сценариях за токеном всегда приходят от имени какого-то пользователя (т.е. передают credentials этого пользователя), а уже потом, в зависимости от типа запроса или выдают токен на этого пользователя или на того, чей токен мы вложили в запрос.

Нам же нужен вариант:
1. Пришел за 1-ым токеном с credentials пользователя
2. Пришел за 2-м токеном с первым, при этом или совсем без credentials или с токеном в их качестве.
Re[4]: Вариант №2 "Ручное" получение токена
От: WSA  
Дата: 04.02.14 06:52
Оценка:
Здравствуйте, Михаил.

WSA>>Что касается внезапного запрета пользователю входа. Нверное или полагаться на относительно небольшой срок действия токена, или реализовывать синхронизацию(но это серьёзный подход, который не всегда оправдан, если не критично)


WSA>>А что если расшифровывать токен на клиенте, для целей клиента, но передавать на RP токен зашифрованный, который было получен от STS??

МР>На самом деле, а надо ли шифровать токены при передаче на RP? Если там нет критически важной информации, то может ну его?
МР>Я в продолжении второй части статьи
Автор: Михаил Романов
Дата: 02.02.14
именно такой вариант показывал — токен без сессионных ключей уже не требует обязательного шифрования и, как следствие, мы можем его читать где угодно, а сама защита осуществляется с помощью защиты канала.



Наверное в данный момент мне придётся пойти по предложеному вами пути и отказаться от шифрования токенов на уровне сообщений, а пользоваться только шифрованием на уровне транспорта.
Потому что схема перестаёт работать, если на клиенте не установлен сертификат с закрытым ключём, для шифрования токена, при передаче на RP... Этот сертификат задаётся на клиенте в разделе:

<system.identityModel.services>
    <federationConfiguration>
      <!--<serviceCertificate>
        <certificateReference x509FindType="FindByThumbprint" findValue="..." storeLocation="LocalMachine" storeName="My" />
      </serviceCertificate>-->
    </federationConfiguration>
  </system.identityModel.services>



И когда клиента запускается на машине где нет данного сертификата, схема рассыпается.... Сейчас попробую использовать токен клиента, или токен общий для всех RP.. без шифрования уровня сообщений.
Но также буду изучать тему токена в роли Credentials... ну и естественно ждать продолжения вашей статьи. Спасибо!
Re: Вариант №3 Делегирование (имперсонация) на базе токенов
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 08.02.14 06:21
Оценка: 3 (1)
Еще раз добрый день, Сергей

Продолжаем изучать WIF и Thinktecture IdentityServer (кстати, отдельное спасибо за наводку на этот продукт — случись сейчас мне делать заново SSO, я бы, наверняка, не раздумывая выбрал его, а не стал бы разрабатывать полностью свое решение на "голом" WIF).

Итак, в чем идея третьего подхода? Она примерно такова:

Сразу же стоит оговориться, что в качестве "Пользователя B" не обязательно должен выступать человек. Это может быть, например, приложение, т.е. пользователь проходит аутентификацию, а затем приложение обращается от его имени к сервисам.
Понятно, что для Desktop приложений это схема выглядит несколько притянутой за уши, просто потому, что credentials клиентского ПО вечь практически эфемерная (их получить можно практически всегда), а вот для серверных приложений (например, когда пользователь работает с Web-фронтендом, который может обращаться к разным сервисам от его имени) уже приобретает смысл.

Итак, попробуем реализовать эту схему. В качестве "Пользователя B" у нас будет приложение.

Сразу же возникает вопрос — какие credentials использовать для аутентификации приложения. В принципе, никто не мешает делать это с помощью стандартных User/Password, однако как-то так сложилось, что для этой задачи чаще используются сертификаты (может просто мне такие решения чаще попадались, не знаю, принципиальной разницы, имхо, тут нет).

Однако, в случае использования Thinktecture мы натыкаемся на один тонкий момент: у него единое пространство пользователей, части из которых можно дать право имперсонации. Нам же нужен вариант с 2-я непересекающимися группами:

К сожалению, я не нашел варианта по-простому сделать это на базе Thinktecture. Логика обработки запросов на делегирование там жестко прописана в классе Thinktecture.IdentityServer.TokenService.TokenService, а способа поменять его через конфигурацию, к сожалению, нет (хотя в целом, точек расширения ребята оставили довольно много).
Т.е. сделать это можно даже не спускаясь до уровня WIF, но придется немного покопаться в коде Thinktecture

Второй момент состоит в том, что UI связывания пользователей и сертификатов почему-то не вынесен на главную страницу, хотя все для его работы есть. Но это не проблема, нужно просто знать что найти его можно по пути https://сервер STS/путь на сервере/Admin/ClientCertificate

В общем, делаем следующее:
1. Создаем пользователя (напримен с именем Desktop Client)

2. Регистрируем для него сертификат (напоминаю, что путь до страницы с этой формой придется ввести руками)

3. И говорим, что данный пользователь имеет право имперсонации (это делается на странице Identity Delegation)


Теперь займемся клиентом. В app.config нам нужно будет указать новую настройку биндинга для аутентификации по сертификату (я привожу только секцию <system.serviceModel>, важные места — полужирным)
<system.serviceModel>
  <bindings>
    <ws2007FederationHttpBinding>
      <binding name="Service">
        <security mode="TransportWithMessageCredential" >
          <message issuedKeyType="BearerKey">
            <issuer address="https://мой_STS/IdSrv/issue/wstrust/mixed/certificate"
                    binding="ws2007HttpBinding" bindingConfiguration="ForActAs" />
          </message>
        </security>
      </binding>
    </ws2007FederationHttpBinding>
    <ws2007HttpBinding>
      <binding name="ForIssue">
        <security mode="TransportWithMessageCredential">
          <message clientCredentialType="UserName" establishSecurityContext="false" />
        </security>
      </binding>
      <binding name="ForActAs">
        <security mode="TransportWithMessageCredential">
          <message clientCredentialType="Certificate" establishSecurityContext="false" />
        </security>
      </binding>
    </ws2007HttpBinding>
  </bindings>
  <client>
    <endpoint address="https://мой_WCF_Service/WcfService/Service.svc"
        binding="ws2007FederationHttpBinding" bindingConfiguration="Service"
        contract="ServiceReference.Service" name="Service" />
    <endpoint name="STS"
              address="https://мой_STS/IdSrv/issue/wstrust/mixed/username"
              binding="ws2007HttpBinding" bindingConfiguration="ForIssue"
              contract="System.ServiceModel.Security.IWSTrustChannelContract" />
  </client>
</system.serviceModel>


А это код клиента:
var stsChannelFactory = new WSTrustChannelFactory("STS");
stsChannelFactory.Credentials.UserName.UserName = "User1";
stsChannelFactory.Credentials.UserName.Password = "123456";

var client = stsChannelFactory.CreateChannel();
var token = client.Issue(
    new RequestSecurityToken(RequestTypes.Issue)
    {
        AppliesTo = new EndpointReference("urn:Client"),
        KeyType = KeyTypes.Bearer
    }
    );

// Здесь можно токен распаковать и получить данные на клиенте

var wcfServiceFactory = new ChannelFactory<ServiceReference.Service>("Service");
wcfServiceFactory.Credentials.UseIdentityConfiguration = true;
var cert = GetClientCertificate();
wcfServiceFactory.Credentials.ClientCertificate.Certificate = cert;

var channel = wcfServiceFactory.CreateChannelWithActAsToken(token);
Console.WriteLine(channel.GetData());


Процедура получения сертификата GetClientCertificate() может брать его хоть из хранилища, хоть из ресурсов приложения.
Например, самый простой вариант (с поиском по захордкоженному thumbprint в хранилище):
public X509Certificate2 GetClientCertificate()
{
    X509Store store = null;
    try
    {
        store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadOnly);
        return
            store.Certificates.Find(X509FindType.FindByThumbprint, "19B14A554EDHYF5D7188C88014779F57193905C2", false)
                .OfType<X509Certificate2>()
                .Single();
    }
    finally
    {
        if (store != null)
            store.Close();
    }
}


Вот, что получилось. Увы, вариант не идеальный — одна из проблем, что исходные токены можно получать прямо на имя пользователя Desktop Client (для серверного решения ключ был бы закрыт от посторонних, а на клиенте это не получится).
Если покопать в коде сервера, все это решить можно, но работы на порядок больше, конечно.
Re[2]: Вариант №1 "Чистый" WCF
От: WSA  
Дата: 20.02.14 11:10
Оценка: 12 (1)
Михаил, здравтсвуйте.
Нашёл тут одну полезную фичу, возможно вам будет полезно тоже.

МР>Ну и осталось привести код сервиса

МР>Там ничего сложного, просто показываю как добраться до переданных Claims. В отличие от WIF 3.5, WIF 4.5 не меняет Principal текущего потока (хотя может где-то и есть такая опция), а предлагает как и в целом в WCF все получать через OperationContext

МР>
МР>namespace WcfService
МР>{
МР>    [ServiceContract]
МР>    public class Service
МР>    {
МР>        [OperationContract]
МР>        public string GetData()
МР>        {
МР>            var princinpal = OperationContext.Current.ClaimsPrincipal;
МР>            var name = princinpal.Identity.Name;
МР>            var email = princinpal.FindFirst(ClaimTypes.Email);
МР>            var webPage = princinpal.FindFirst(ClaimTypes.Webpage);

МР>            return string.Format("User: {0}, mail: {1}, web page: {2}", name, 
МР>                email == null ? "" : email.Value,
МР>                webPage == null ? "" : webPage.Value);
МР>        }
МР>    }
МР>}
МР>




Когда в настройках WCF-сервиса мы укажем

<behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- ... -->
          <serviceCredentials  useIdentityConfiguration="true"/>
<serviceAuthorization principalPermissionMode="Always"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>


в результате появляется возможность авторизировать доступ пользователя к операции WCF-сервиса через аттрибуты операции:


        [PrincipalPermission(SecurityAction.Demand, Role = @"admin")]
        public List<Items> GetItems()



В добавок System.Threading.Thread.CurrentPrincipal становится вместо WindowsPrincipal — ClaimsPrincipal'ом


С уважением, Сергей
Re[3]: Вариант №1 "Чистый" WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 20.02.14 11:32
Оценка:
Сергей, добрый день

Да, существенное дополнение. Спасибо!

WSA>В добавок System.Threading.Thread.CurrentPrincipal становится вместо WindowsPrincipal — ClaimsPrincipal'ом

Меня это (что ребята отказались от выставления System.Threading.Thread.CurrentPrincipal, как это было в WIF 3.5 и поломали тем самым совместимость) — очень смущало. А оказалось, что я просто не досмотрел настройки.

WSA>в результате появляется возможность авторизировать доступ пользователя к операции WCF-сервиса через аттрибуты операции:

Конкретно у нас это оказалось не актуальным, но по причине того, что мы не смогли использовать стандартный claim для ролей — у нас чуть более сложная система прав и только плоским спиком ролей её не опишешь, пришлось использовать свои типы Claims, свои атрибуты разметки и писать свой менедежер авторизации. Впрочем, это оказалось совсем не сложно, зато на порядок гибче (что для нас было существеннее всего).

Но в любом случа — спасибо!

P.S. Есть не нулевая вероятность, что я таки доберусь (с подачи нашего учебного центра) до полноценного цикла статей/внутреннего курса по WIF (и ряду других тем), поэтому любые новые знания и находки очень к месту.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.