Архитектура WCF, поэтому я могу работать с обратными вызовами и аутентифицироваться на AD
Извините за длинный заголовок.
Это о конфигурации и безопасности WCF. У меня есть следующий сценарий:
- один сервер (IIS)
- N клиентов (приложения WPF)
- веб-сервисы для связи между клиентами и сервером
- все находится в одной локальной сети и в одном домене
- Мне нужна дуплексная связь, чтобы сервер мог уведомлять всех клиентов (без опроса, но с использованием обратных вызовов WCF)
- учетные данные пользователя (логин / пароль) управляются активным каталогом
- аутентификация пользователя "на пользователя", а не "на учетную запись Windows" и "на компьютер"
В идеале мне бы хотелось иметь 2 разных сервиса:
Тот, который требует сеанса WCF, чтобы я мог использовать обратные вызовы:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Subscribe();
[OperationContract(IsOneWay = false, IsTerminating = true)]
void Unsubscribe();
}
Тот, который этого не делает, поэтому я могу использовать старый добрый способ для написания сервисов без сохранения состояния, эффективных и поддерживаемых: каждый вызов WCF проходит аутентификацию, авторизацию и создает на стороне сервера новый свежий экземпляр реализации Сервиса:
[ServiceContract]
public interface IService2
{
[OperationContract]
int DoSomeStuff();
}
Дело в том, что мы хотим, чтобы каждый клиент проходил аутентификацию в активном каталоге. Поэтому при запуске клиентского приложения каждый клиент должен указать логин, пароль и домен. Проблема в том, что аутентификация AD довольно трудоемка:
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "theDomain"))
{
if (!pc.ValidateCredentials("theUser", "thePassword"))
{
throw new SecurityException();
}
}
Моей первой идеей было использовать IService1
сервис (который отвечает за отправку обратных вызовов) как сервис аутентификации:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract(IsOneWay = false, IsInitiating = true)]
void Login(string login, string password, string domain);
[OperationContract(IsOneWay = false, IsTerminating = true)]
void Logout();
}
Так что внутри Login
В ходе реализации операции мы могли проверять логин / пароль пользователя по отношению к AD только один раз и полагаться на сеанс WCF для следующих запросов WCF от клиента. Проблема в том, что я не хочу делать другие запросы против IService1
сервис из-за режима сеанса, который Required
, а также InstanceContextMode
то есть PerSession
(да, уродливый шаблон синглтона клиент-прокси, которого я хочу избегать для других операций, кроме только уведомлений входа / выхода / клиента).
Итак, вопрос в том, как я могу построить и настроить свои сервисы, поэтому:
- Мне не нужно запрашивать AD по каждому запросу WCF
- У меня есть серверный обратный вызов
- У меня есть только одноэлементный шаблон / требуемый сеанс / контекст экземпляра на сеанс для службы обратного вызова (не "фактический"
IService2
оказание услуг)
Я думал о wsHttpBinding
за IService2
а также netTcpBinding
за IService1
, Что касается выбора безопасности передачи (транспорта или сообщения) и выбора типа учетных данных (Windows, UserName,..?), Я не уверен.
Любая помощь будет принята с благодарностью.
2 ответа
Если я правильно поняла:
- вам нужна услуга (1)
- отвечает за аутентификацию, токен, что угодно и
- также обеспечивает обратный вызов за сеанс
- вам нужен фактический рабочий сервис, который
- может сделать обработку
- может инициировать обратный вызов
- может работать только если аутентифицирован
Насколько я вижу, проверка подлинности связана с вопросом о сеансе, так что это не сложная проблема (будет использовать ADFS, если вам нужна федерация (идентификация на основе токена) и AD одновременно). Я попробовал это с этой настройкой с двумя простыми службами
- service1: wsHttpBinding (ради сессии)
- service2: basicHttpBinding (для простоты может также использоваться net tcp)
Важным моментом было убедиться, что экземпляр второго не связан с первым (на основе сеанса).
Учитывая, что каким-то образом конец обработки (ответственность service2) должен инициировать обратный вызов (ответственность service1), нет способа избежать совместного использования некоторой информации обратного вызова (что также означает совместное использование либо идентификатора сеанса, либо чего-либо, идентифицирующего экземпляр активного сеанса service1).,
Я попробовал самый простой подход с использованием кэша для хранения экземпляра обратного вызова против ключа сеанса. (Я знаю много контрпримеров, почему это зло, но это было только для доктора). Таким образом, жизненные циклы двух сервисов были разъединены, и, тем не менее, можно было вызвать обратный вызов.
Я уверен, что создание правильного токена и привязка проверки токена к службе обработки хорошо задокументированы (ADFS и ее потребители).
Тем не менее, я думаю, что ключевым моментом является использование общего обратного вызова там.
Дайте мне знать, если я полностью не понял вашу проблему.
Спасибо Николай
Вы можете попробовать это:
Создайте специальный инспектор сообщений, который принимает два параметра - UserName и PWD, которые вы аутентифицируете на своем сервере AD при первом обращении клиента к вашему хосту. Но прежде чем возвращать ответ клиенту, добавьте ограниченный по времени файл cookie. Когда клиент запрашивает снова, проверьте наличие cookie в OperationContext запроса и пропустите аутентификацию AD.
Об этом решении нужно знать две вещи. Во-первых, когда вы используете инспектор сообщений, каждый запрос (не только вход в систему) будет проходить процедуру. Вот как они работают. Но из-за проверки cookie вы можете пропустить последующие проверки AD, если cookie действителен. И это устраняет необходимость процедуры входа в систему.
Что-то вроде этого:
public class SchemaLoggingMessageInspector : Dispatcher.IDispatchMessageInspector
{
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel,
System.ServiceModel.InstanceContext instanceContext) {
var cookieHeader = WebOperationContext.Current.IncomingRequest.Headers[System.Net.HttpRequestHeader.Cookie];
if (!String.IsNullOrEmpty(cookieHeader)){
var match = cookieHeader.Split(';').Select(cookie => cookie.Split('=')).FirstOrDefault(kvp => kvp[0] == "BigCookie");
if (match != null)
{
return True
}
}
if (WcfBaseFunctionality.eSecurityType == SecurityEnum.authentication) {
string username = AuthenticationBehavior.GetHeaderData("Username");
string password = AuthenticationBehavior.GetHeaderData("Password");
if (ValidateAuthentication(username, password){
WebOperationContext.Current.OutgoingResponse.Headers[System.Net.HttpResponseHeader.SetCookie] = Cookie="BigCookie";
return True;
}else{
return False;
};
}
return False;
}
private bool ValidateAuthentication(string UserName, string PWD)
{
//add code to check ID/password against AD server
return true;
}
}
Что касается обратного вызова клиента, я никогда не пробовал этого. Сожалею. Удачи с вашим веб-сервисом.