Статьи

Внедрение межсетевого экрана службы WCF, часть II из N

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

Как я уже отмечал ранее, WCF предоставляет довольно много точек расширения на маршруте, который проходит сообщение от прибытия в службу до точки WCF, вызывающей фактический метод в экземпляре службы. Некоторые из этих точек расширения, например, являются возможными кандидатами в Service Firewall.

  • Фильтр контракта — Фильтр контракта отвечает за направление сообщений в соответствующий контракт. Это должен быть подкласс MessageFilter . Похоже, что контрактный фильтр является хорошим вариантом, поскольку он перехватывает вызов довольно рано, поэтому он, вероятно, будет самым быстрым вариантом. Также его название (фильтр ..) подразумевает, что это хороший вариант
  • Инспектор сообщений — Инспектор сообщений отвечает за просмотр или изменение сообщений при входе в службу и выглядит как естественный кандидат на работу. Существует два вида инспекторов сообщений: те, кто просматривает сообщения на стороне клиента (реализуют интерфейс IClientMessageInspector ) и те, которые смотрят на стороне сервера (реализуют IDispatchMessageInspector ). Похоже, что последний тип инспектора нам нужен здесь.
  • Диспетчер авторизации услуг — отвечает за оценку политик, заявлений и т. Д. Клиента, чтобы удостовериться, что вызов действителен с точки зрения безопасности. Похоже, это будет хороший класс для использования в реальном брандмауэре службы. Кажется, это не очень подходит для того, что нам нужно здесь.

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

Я взял класс, подготовленный для некоторых интеграционных тестов EventBroker, и создал несколько расширений, которые взаимодействуют с ними. Вот часть кода установки среды:

testServer = new Tester();
service1 = new ServiceHost(testServer, new Uri(string.Format("http://localhost:{0}", TestServerPort)));
var binding = new WebHttpBinding
{
ReaderQuotas = { MaxArrayLength = 600000 },
MaxReceivedMessageSize = 800000,
MaxBufferSize = 800000

};

var ep = service1.AddServiceEndpoint(typeof(TestingContract), binding, string.Format("http://localhost:{0}/S1", TestServerPort));
ep.Behaviors.Add(new WebHttpBehavior());
ep.Behaviors.Add(new InspectorBehavior());
service1.Authorization.ServiceAuthorizationManager = new TestAuthorizer();
var cp = service1.AddServiceEndpoint(typeof(ImContract), binding, string.Format("http://localhost:{0}/Control", TestServerPort));
cp.Behaviors.Add(new WebHttpBehavior());

Вышеупомянутые две красные линии — это те, которые отвечают за внедрение POC, а InspectorBehavior отвечает за вставку ContractFilter, а MessageInspector и TestAuthorizer — реализация теста диспетчера авторизации.

Нам также нужен код для вызова события:

public void SendMessage()
{
var evnt = new TestingEvent { sagaId = Guid.NewGuid() };

moqRA.Expect(x => x.GetChannel<TestingContract>(evnt.sagaId, true)).Returns(channel1);
moqRA.Expect(x => x.GetChannel<TestingContract2>(evnt.sagaId, true)).Returns(channel3);

eb.BeginNewSagaEvent(evnt.sagaId, evnt);
eb.CloseSaga(evnt.sagaId);

}

И теперь мы можем посмотреть на разные варианты. InspectorBehavior — это просто вспомогательный класс для фильтрации и / или инспектора до конечного элемента. (Диспетчер авторизации настраивается на уровне обслуживания (т. Е. Для всех конечных точек))

public class InspectorBehavior : IEndpointBehavior
{

public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
throw new NotImplementedException();
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
var inspector = new TestInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
endpointDispatcher.ContractFilter = new TestFilter(endpointDispatcher.ContractFilter);

}

public void Validate(ServiceEndpoint endpoint)
{
}

Первое, что я попробовал, был «ContractFilter». Это на самом деле очень просто в использовании. Вы наследуете от MessageFilter, и вам нужно переопределить два метода «Match». Тот, который принимает буфер, и тот, который принимает (WCF) Сообщение. WCF вызывает метод Match, который принимает сообщение.

Класс сообщений WCF интересен в том смысле, что он имеет функцию одноразового касания. т.е. только один кусок кода может прочитать / скопировать его, и следующий фрагмент кода, который попытается сделать то же самое, потерпит неудачу

Таким образом, метод соответствия вы можете сделать что-то вроде следующего:

public override bool Match(Message message)
{
var buffer = message.CreateBufferedCopy(Int32.MaxValue);
message = buffer.CreateMessage();
var r = buffer.CreateMessage().GetReaderAtBodyContents();
.
.
.
}

Что в основном означает получение буфера сообщения, создание одной копии для сохранения и получение другой копии для внутреннего использования и работа с этим для анализа и проверки фактического сообщения. К сожалению, это на самом деле не работает — параметр сообщения не передается как ref, поэтому исходное сообщение теряется в первой строке метода и все. Обратите внимание, что вы можете получить доступ к заголовочной части сообщения без проблем, однако это не очень подходит для того, что я пытаюсь сделать.

Следующее, что я посмотрел на MessageInspector. Снова реализовать это довольно просто, вам просто нужно реализовать интерфейс IDispatchMessageInspector. Этот интерфейс имеет два метода BeforeSendReply и AfterReceiveRequest. Мы рассмотрим метод AfterReceiveRequest. Снова попробуем трюк копирования сообщения:

public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
var buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
var temp = buffer.CreateMessage().GetReaderAtBodyContents();
.
.
.
}

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

Таким образом, использование MessageInspector является удобной опцией. Это очень хорошо, если вы хотите изменить входящее сообщение, но выдает исключение, если сообщение плохое, не очень чистое

Что приводит нас к нашему третьему варианту диспетчера авторизации, который, на удивление, оказался лучшим вариантом

public class TestAuthorizer :ServiceAuthorizationManager
{
public override bool CheckAccess(OperationContext operationContext, ref Message message)
{
var autorized= base.CheckAccess(operationContext, ref message);
var buffer = message.CreateBufferedCopy(Int32.MaxValue);
message = buffer.CreateMessage();
var testMessage = buffer.CreateMessage();
.
.
.
return autorized;
}

}

 

Как и инспектор сообщений, он получает сообщение как ref и, подобно фильтру, позволяет одному ответу «да / нет» решить, следует ли продолжить сообщение или отказаться от него. Кроме того, он уведомляет клиента о том, что сообщение было отклонено, если это именно то, что вы решите сделать (в используемом мной WebHttpBinding это означает неверный код возврата 400 запросов)

Итак, мы рассмотрели некоторые варианты реализации Service Firewall и кратко рассмотрели их различные варианты поведения. В следующей части этой серии статей мы рассмотрим некоторые фактические реализации, которые я сделал.