Вопрос уже поднимался, но нормального ответа так и не нашёл...
Итак, диспозиция:
— есть WCF-сервис
— есть разнообразные клиенты, к нему обращающиеся (всё на .net)
— используемая привязка: webHttp, в перспективе перейти на netHttp, для некоторых клиентов — на tcp или pipes
— Windows-аутентификация не используется, пользователи с паролями лежат в БД
— требуется: юзер выполняет вход — вводит логин-пароль, при успешном входе все последующие запросы к сервису должны считаться от имени этого вошедшего пользователя
— WCF-сессии не используются, сервисы stateless
— работа с сервисами через ChannelFactory (не Service Reference)
Схема реализации в голову приходит такая:
— для входа юзера приложение дёргает какой-то метод сервиса, в ответ (в случае успеха) получает какой-то token/ticket/coockie
— далее этот token/ticket/coockie как-то передаётся во все запросы
— на серверной стороне этот token/ticket/coockie извлекается, проверяется, по нему определяется что за юзер
Это нормальный подход? Есть ли другие варианты?
Существуют ли какие-то готовые решения для такого?
Пока нашёл два пути реализации:
1. использовать хидеры запроса (http или soap): в них для каждого обращение к сервису клиентское приложение кладёт token/ticket/coockie, на серверной стороне используется свой ServiceAuthorizationManager.
Подход работает, но в целом напрягает что не используются стандартные механизмы — например, ServiceSecurityContext говорит что пользователь не аутентифицирован. Соответственно, авторизацию делаем руками. В общем, какое-то велосипедное решение.
2. использовать стандартные Credentials: кладём token/ticket/coockie в channelFactory.Credentials.UserName. На сервере используем свой UserNamePasswordValidator.
Этот подход ещё не применяли, но в целом кажется получше — ServiceSecurityContext уже показывает что пользователь аутентифицирован, скорее всего заработают стандартные механизмы авторизации. Но всё равно остаётся ощущение неверного использования — ведь channelFactory.Credentials.UserName подразумевает указание имени пользователя и пароля, а мы используем его для других целей.
Ещё смотрел на System.Web.ApplicationServices.AuthenticationService — но похоже это что-то сильно завязанное на http и asp.net, с другими протоколами наверно не взлетит.
Давайте так, обычно с точки зрения передачи Credentials выбирают один из 2-х подходов:
Передавать credentials в каждом запросе. Этот режим является стандартным для почти все встроенных bindings
Передавать credentials один раз, получать некий security token и затем в каждом запросе передавать этот токен (пока не истечет время жизни токена)
Честно говоря, если нет каких-то серьезных оснований для иного я бы предложил вам остановиться на первом варианте. Т.е. работать по следующей схеме:
Во всех протоколах перейти на <security mode="Message"> и <message clientCredentialType="UserName"> (увы, не поддерживается в Named Pipes)
Аутентификацию на всех сервисах проводить используя тот самый UserNamePasswordValidator, только проверять он будет не токен, а непосредственно сами пользователя и пароль
Вторая схема (с выдачей security token) полезна в случае, если вы выделяете один authentication server, который обслуживает множество прикладных сервисов. Это особенно полезно когда:
Сервисы разрабатываются разными организациями (и не у каждой есть лоступ с credentials пользователей)
Схемы разрабатываются разными группами — тогда они становятся менее зависимыми от инфраструктуры (менее связанные).
Для этой схемы есть встроенная поддержка на базе wsFederationHttpBinding и ws2007FederationHttpBinding.
Я описывал некоторые моменты такой работы в постах на RSDN, возможно, вам будет интересно (правда там использовался готовый Identity server, но во-первых, он очень расширяемый и вы можете адаптировать тот код под себя, а во-вторых, создавть полностью свой WS-Federation или WS-Trust сервер не так и сложно).
По поводу своих вариантов передачи и вашхи вопросов
P_K>1. использовать хидеры запроса (http или soap): в них для каждого обращение к сервису клиентское приложение кладёт token/ticket/coockie, на серверной стороне используется свой ServiceAuthorizationManager.
Вообще-то довольно распространенный вариант.
Почему у вас не создается нормальный ServiceSecurityContext — не могу сказать. На сколько я помню, можно указать что вы будете создавать полностью свой IPrincipal и формировать его как вым заблагороссудится. А уже ServiceSecurityContext должен подхватывать его.
P_K>2. использовать стандартные Credentials: кладём token/ticket/coockie в channelFactory.Credentials.UserName. На сервере используем свой UserNamePasswordValidator.
Не стоит так делать. В вашем коде потом сам черт сломит ногу.
Уж лучше, как я писал выше передавайте через этот механизм сами credentials.
Здравствуйте, Михаил Романов, Вы писали:
МР>Честно говоря, если нет каких-то серьезных оснований для иного я бы предложил вам остановиться на первом варианте. Т.е. работать по следующей схеме: МР>
МР>Во всех протоколах перейти на <security mode="Message"> и <message clientCredentialType="UserName"> (увы, не поддерживается в Named Pipes) МР>Аутентификацию на всех сервисах проводить используя тот самый UserNamePasswordValidator, только проверять он будет не токен, а непосредственно сами пользователя и пароль МР>
Это придётся в клиенте постоянно хранить в памяти пароль, который ввёл юзер. Для случая с ASP.NET неудобно.
<security mode="Message"> и <message clientCredentialType="UserName"> — не запустилось с netHttpBinding. Предлагает использовать TransportWithMessageCredential, что в свою очередь требует SSL...
МР>Вторая схема (с выдачей security token) полезна в случае, если вы выделяете один authentication server, который обслуживает множество прикладных сервисов. Это особенно полезно когда: МР>
МР>Сервисы разрабатываются разными организациями (и не у каждой есть доступ с credentials пользователей) МР>Схемы разрабатываются разными группами — тогда они становятся менее зависимыми от инфраструктуры (менее связанные). МР>
Отдельный Identity-сервер тоже не охота городить. Тем более что МР>Но! Этот вариант только для HTTP транспорта.
P_K>>1. использовать хидеры запроса (http или soap): в них для каждого обращение к сервису клиентское приложение кладёт token/ticket/coockie, на серверной стороне используется свой ServiceAuthorizationManager. МР>Вообще-то довольно распространенный вариант. МР>Почему у вас не создается нормальный ServiceSecurityContext — не могу сказать. На сколько я помню, можно указать что вы будете создавать полностью свой IPrincipal и формировать его как вам заблагороссудится. А уже ServiceSecurityContext должен подхватывать его.
Да вот как-то не разобрался как создавать полностью свой IPrincipal и подсовывать его в ServiceSecurityContext. Спасибо за наводку, надо покопать ещё в этом направлении. Если есть ссылки по теме — буду благодарен.
Здравствуйте, Poul_Ko, Вы писали:
P_K>Это придётся в клиенте постоянно хранить в памяти пароль, который ввёл юзер. Для случая с ASP.NET неудобно.
Ну для Desktop и мобильных клиентов это нормально.
Что касается web-решений, то в любом случае вам нужно где-то хранить или сами credentials, или токен. Я не вижу между этими двумя случаями принципиальной разницы.
Варианты хранения понятны: хранилище сессионных данных на сервере или в зашифрованном виде на клиенте (куки/скрытые поля страниц/в коде JS/...)
P_K><security mode="Message"> и <message clientCredentialType="UserName"> — не запустилось с netHttpBinding. Предлагает использовать TransportWithMessageCredential, что в свою очередь требует SSL...
Ну всё верно, пароль в Message передается в открытом виде, его надо защищать.
Поэтому либо TransportWithMessageCredential поверх SSL, либо включить шифрование сообщений (но надо будет указывать сертификат на стороне сервисов)
Кстати, поинтересуюсь — а почему вы хотите использовать netHttpBinding? По сравнению с basicHttpBinding и wsHttpBinding я вижу только одно преимущество — поддержку дуплексного режима работы (но для того же WS есть wsDualHttpBinding).
P_K>Отдельный Identity-сервер тоже не охота городить. Тем более что МР>>Но! Этот вариант только для HTTP транспорта.
А что смущает?
Если наличие еще одной точки деплоя, то это можно решить, включив код Identity Server (причем можно оставить только часть для поддержки сервисов) в свои сервисы.
Ну и вновь поинтересуюсь — а каковы резоны отказа от HTTP в пользу TCP и уж тем более Named Pipes (его обычно рекомендуют использовать лишь для межпроцессных вызовов, а не для передачи по сети)?
P_K>Да вот как-то не разобрался как создавать полностью свой IPrincipal и подсовывать его в ServiceSecurityContext. Спасибо за наводку, надо покопать ещё в этом направлении. Если есть ссылки по теме — буду благодарен.
Возможно, я за давностью лет что и позабыл. Не исключено, что ServiceSecurityContext создается до того как отработает IAuthorizationPolicy — надо тестировать.
Попробуйте посмотреть, например, вот такую статью Custom Authorization in WCF — с точки зрения кода там делается ровно то, что я предлагал: создается свой IPrincipal (на самом деле это не обязательно, можно создавать экземпляры стандартного GenericPrincipal) и передается дальше по WCF pipeline.
Единственная разница — в статье предполагается, что до того, как сработала полититка авторизации, уже отработл код отвечающий за аутентификацию и он создал готовые Identity, которые сохранил в переменных pipeline. А я бы предложил попробовать и Identity создать самому.
Здравствуйте, Михаил Романов, Вы писали:
P_K>><security mode="Message"> и <message clientCredentialType="UserName"> — не запустилось с netHttpBinding. Предлагает использовать TransportWithMessageCredential, что в свою очередь требует SSL... МР>Ну всё верно, пароль в Message передается в открытом виде, его надо защищать. МР>Поэтому либо TransportWithMessageCredential поверх SSL, либо включить шифрование сообщений (но надо будет указывать сертификат на стороне сервисов) МР>Кстати, поинтересуюсь — а почему вы хотите использовать netHttpBinding? По сравнению с basicHttpBinding и wsHttpBinding я вижу только одно преимущество — поддержку дуплексного режима работы (но для того же WS есть wsDualHttpBinding). МР>Ну и вновь поинтересуюсь — а каковы резоны отказа от HTTP в пользу TCP и уж тем более Named Pipes (его обычно рекомендуют использовать лишь для межпроцессных вызовов, а не для передачи по сети)?
Чтобы ответить на ваши вопросы опишу общую архитектуру.
Есть какое-то "ядро" системы, есть различные части, использующие это ядро — назовём их клиентами. Клиенты бывают внешние — типа десктопного приложения, и внутренние — другой веб-сервер, другие службы.
Для случая внешних клиентов защита, SSL и прочее имеют смысл. Хотя особых требований и нет, клиенты пока в локальной сети. netHttpBinding рассматривается тут в виде оптимизации, он же вроде использует бинарный формат. Сейчас используется webHttpBinding.
А вот для случая внутренних клиентов, которые хостятся на том же сервере, не вижу смысла в шифровании, XML-сериализации и прочем оверхеде. Простейшие пайпы тут в самый раз.
Наверно моя проблема в том, что для внутренних клиентов аутентификация — это не то же самое, что для внешних. Склоняюсь к мнению, что для внешних можно использовать поход с передачей credentials, а для внутренних — использовать решение с хидерами.
P_K>>Отдельный Identity-сервер тоже не охота городить. Тем более что МР>>>Но! Этот вариант только для HTTP транспорта. МР>А что смущает? МР>Если наличие еще одной точки деплоя, то это можно решить, включив код Identity Server (причем можно оставить только часть для поддержки сервисов) в свои сервисы.
Это же нужно изучать новую технологию, переписывать/адаптировать под неё код, наступать на грабли которые там разложены... Поразбираться конечно интересно, но не в текущей ситуации.
Примерно понятно.
Ниже пара моих комментариев
P_K>Есть какое-то "ядро" системы, есть различные части, использующие это ядро — назовём их клиентами. Клиенты бывают внешние — типа десктопного приложения, и внутренние — другой веб-сервер, другие службы. P_K>netHttpBinding рассматривается тут в виде оптимизации, он же вроде использует бинарный формат. Сейчас используется webHttpBinding.
Да, заявленно, что один из форматов — бинарнный. На сколько это существенно влияет на объемы сообщений — не известно. Например, с .Net 4.5 заявлено, что при выставлении соответсвующих заголовков в запросе с клиента WCF сжимает исходящий трафик. А в более ранних версиях такое поведение обеспечивалось при хостинге под IIS (и установке соответсвующих модулей).
P_K>Наверно моя проблема в том, что для внутренних клиентов аутентификация — это не то же самое, что для внешних. Склоняюсь к мнению, что для внешних можно использовать поход с передачей credentials, а для внутренних — использовать решение с хидерами.
Скорее всего так и есть. Реальные аутентификационные данные от пользователей имеет смысл передавать только если есть необходимость выполнять операции в контексте какого-то пользователя.
С другой стороны, даже в таком случае вы можете принят за основу, что при вызове сервис-сервис у вас устанавливается доверенная среда и вы можете доверять вызвавшему сервису (он уже проверил credentials пользователя)). И тогда вы можете просто передавать в заголовках (чтобы не менять сигнатуры методов) просто идентификатор пользователя. Т.е. делать этакую имперсонацию.
А по поводу аутентифкации сервисов (т.к. вызов сервис-сервис иногда всё же надо защищать): здесь можно использовать аутентификацию на основе сертификатов.
P_K>Это же нужно изучать новую технологию, переписывать/адаптировать под неё код, наступать на грабли которые там разложены... Поразбираться конечно интересно, но не в текущей ситуации.
Понятно, разумно.
Да, если вся остальная инфраструктура уже готова, добавить проброс пары заголовков не составит большого труда, а вот подключение нового сервиса будет явно затратнее.