Аутентификация в WCF
От: Poul_Ko Казахстан  
Дата: 27.11.15 04:17
Оценка:
Вопрос уже поднимался, но нормального ответа так и не нашёл...

Итак, диспозиция:
— есть 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, с другими протоколами наверно не взлетит.
Brainbench transcript #6370594
Re: Аутентификация в WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 27.11.15 10:26
Оценка: 20 (4) +1
Здравствуйте, Poul_Ko, Вы писали:

Давайте так, обычно с точки зрения передачи Credentials выбирают один из 2-х подходов:

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

Вторая схема (с выдачей security token) полезна в случае, если вы выделяете один authentication server, который обслуживает множество прикладных сервисов. Это особенно полезно когда:

Для этой схемы есть встроенная поддержка на базе wsFederationHttpBinding и ws2007FederationHttpBinding.
Я описывал некоторые моменты такой работы в постах на RSDN, возможно, вам будет интересно (правда там использовался готовый Identity server, но во-первых, он очень расширяемый и вы можете адаптировать тот код под себя, а во-вторых, создавть полностью свой WS-Federation или WS-Trust сервер не так и сложно).

Но! Этот вариант только для HTTP транспорта.

По поводу своих вариантов передачи и вашхи вопросов

P_K>1. использовать хидеры запроса (http или soap): в них для каждого обращение к сервису клиентское приложение кладёт token/ticket/coockie, на серверной стороне используется свой ServiceAuthorizationManager.

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

P_K>2. использовать стандартные Credentials: кладём token/ticket/coockie в channelFactory.Credentials.UserName. На сервере используем свой UserNamePasswordValidator.

Не стоит так делать. В вашем коде потом сам черт сломит ногу.
Уж лучше, как я писал выше передавайте через этот механизм сами credentials.
Re[2]: Аутентификация в WCF
От: Poul_Ko Казахстан  
Дата: 27.11.15 15:09
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

МР>Честно говоря, если нет каких-то серьезных оснований для иного я бы предложил вам остановиться на первом варианте. Т.е. работать по следующей схеме:

МР> Это придётся в клиенте постоянно хранить в памяти пароль, который ввёл юзер. Для случая с ASP.NET неудобно.
<security mode="Message"> и <message clientCredentialType="UserName"> — не запустилось с netHttpBinding. Предлагает использовать TransportWithMessageCredential, что в свою очередь требует SSL...


МР>Вторая схема (с выдачей security token) полезна в случае, если вы выделяете один authentication server, который обслуживает множество прикладных сервисов. Это особенно полезно когда:

МР> Отдельный Identity-сервер тоже не охота городить. Тем более что
МР>Но! Этот вариант только для HTTP транспорта.


P_K>>1. использовать хидеры запроса (http или soap): в них для каждого обращение к сервису клиентское приложение кладёт token/ticket/coockie, на серверной стороне используется свой ServiceAuthorizationManager.

МР>Вообще-то довольно распространенный вариант.
МР>Почему у вас не создается нормальный ServiceSecurityContext — не могу сказать. На сколько я помню, можно указать что вы будете создавать полностью свой IPrincipal и формировать его как вам заблагороссудится. А уже ServiceSecurityContext должен подхватывать его.
Да вот как-то не разобрался как создавать полностью свой IPrincipal и подсовывать его в ServiceSecurityContext. Спасибо за наводку, надо покопать ещё в этом направлении. Если есть ссылки по теме — буду благодарен.


В целом, спасибо за ответ, есть над чём подумать!
Brainbench transcript #6370594
Re[3]: Аутентификация в WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 28.11.15 04:15
Оценка:
Здравствуйте, 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 создать самому.
Re[4]: Аутентификация в WCF
От: Poul_Ko Казахстан  
Дата: 28.11.15 13:04
Оценка:
Здравствуйте, Михаил Романов, Вы писали:

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 (причем можно оставить только часть для поддержки сервисов) в свои сервисы.
Это же нужно изучать новую технологию, переписывать/адаптировать под неё код, наступать на грабли которые там разложены... Поразбираться конечно интересно, но не в текущей ситуации.
Brainbench transcript #6370594
Re[5]: Аутентификация в WCF
От: Михаил Романов Удмуртия https://mihailromanov.wordpress.com/
Дата: 28.11.15 13:30
Оценка:
Здравствуйте, Poul_Ko, Вы писали:

Примерно понятно.
Ниже пара моих комментариев

P_K>Есть какое-то "ядро" системы, есть различные части, использующие это ядро — назовём их клиентами. Клиенты бывают внешние — типа десктопного приложения, и внутренние — другой веб-сервер, другие службы.

P_K>netHttpBinding рассматривается тут в виде оптимизации, он же вроде использует бинарный формат. Сейчас используется webHttpBinding.
Да, заявленно, что один из форматов — бинарнный. На сколько это существенно влияет на объемы сообщений — не известно. Например, с .Net 4.5 заявлено, что при выставлении соответсвующих заголовков в запросе с клиента WCF сжимает исходящий трафик. А в более ранних версиях такое поведение обеспечивалось при хостинге под IIS (и установке соответсвующих модулей).

P_K>Наверно моя проблема в том, что для внутренних клиентов аутентификация — это не то же самое, что для внешних. Склоняюсь к мнению, что для внешних можно использовать поход с передачей credentials, а для внутренних — использовать решение с хидерами.

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

А по поводу аутентифкации сервисов (т.к. вызов сервис-сервис иногда всё же надо защищать): здесь можно использовать аутентификацию на основе сертификатов.

P_K>Это же нужно изучать новую технологию, переписывать/адаптировать под неё код, наступать на грабли которые там разложены... Поразбираться конечно интересно, но не в текущей ситуации.

Понятно, разумно.
Да, если вся остальная инфраструктура уже готова, добавить проброс пары заголовков не составит большого труда, а вот подключение нового сервиса будет явно затратнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.