Catch-22 предотвращает защиту WSF-сервиса TCP WCF от WIF; разрушение моего Рождества, психическое здоровье

У меня есть требование защитить потоковый конечный пункт службы WCF net.tcp с использованием WIF. Он должен аутентифицировать входящие вызовы на нашем токен-сервере. Услуга передается потоком, поскольку она предназначена для передачи больших объемов данных.

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

Почему это невозможно? Здесь Catch-22.

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

// people around here hate the Framework Design Guidelines.
var token = Authentication.Current._Token;
var service = base.ChannelFactory.CreateChannelWithIssuedToken(token);
return service.Derp();

Я сказал "no problemo"? Problemo. На самом деле, NullReferenceException style problemo.

"Бро, - спросил я в" Рамочной программе ", - вы даже не проверяете? Рамка была безмолвной, поэтому я разобрался и обнаружил, что

((IChannel)(object)tChannel).
    GetProperty<ChannelParameterCollection>().
    Add(federatedClientCredentialsParameter);

был источником исключения и что вызов GetProperty возвращался null. Итак, WTF? Оказывается, если я включу защиту сообщений и настрою тип учетных данных клиента на IssuedToken, то это свойство теперь существует в ClientFactory (protip: эквивалент "SetProperty" в IChannel, ублюдок).

<binding name="OMGWTFLOL22" transferMode="Streamed" >
    <security mode="Message">
        <message clientCredentialType="IssuedToken"/>
    </security>
</binding>

Сладкое. Больше NRE. Тем не менее, теперь мой клиент ошибается при рождении (по-прежнему любите его, tho). Копая через диагностику WCF (протапывайте: сделайте так, чтобы ваши худшие враги сделали это после того, как раздавили их и водили их перед вами, но прямо перед тем, как наслаждаться жалобами своих женщин и детей), я вижу это из-за несоответствия безопасности между сервером и клиентом.

Запрошенное обновление не поддерживается "net.tcp://localhost: 49627/MyService". Это может быть связано с несогласованными привязками (например, защита включена на клиенте, а не на сервере).

Проверка хостов (снова: раздавить, диск, читать журналы, наслаждаться плачами), я вижу, что это правда

Приложение типа протокола /ssl -tls было отправлено службе, которая не поддерживает этот тип обновления.

"Ну, я, - говорю я, - я просто включу защиту сообщений на хосте!" И я делаю. Если вы хотите знать, как это выглядит, это точная копия конфигурации клиента. Посмотрите вверх.

Результат: Kaboom.

Связывание ('NetTcpBinding', 'http://tempuri.org/') поддерживает потоковое вещание, которое невозможно настроить вместе с безопасностью уровня сообщений. Рассмотрите возможность выбора другого режима передачи или выбора безопасности транспортного уровня.

Итак, мой хост не может быть потоковым и защищенным с помощью токенов. Catch-22.

tl; dr: Как я могу защитить поточную конечную точку WCF net.tcp с помощью WIF???

Ответ 1

WCF имеет несколько областей с потоковой передачей (я смотрю на вас, MTOM 1) из-за фундаментальной проблемы в том, как он не может выполнить предварительную аутентификацию, как большинство людей думают, что должно (это влияет только на последующие запросы для этого канала, а не на первый запрос) Хорошо, так что это не совсем ваша проблема, но, пожалуйста, следуйте за мной, пока я доберусь до вас. Обычно HTTP-запрос работает следующим образом:

  • клиент обращается к серверу анонимно
  • сервер говорит, извините, 401, мне нужна аутентификация
  • клиент обращается к серверу с токеном аутентификации
  • сервер принимает.

Теперь, если вы попытаетесь включить потоки MTOM в конечной точке WCF на сервере, он не будет жаловаться. Но когда вы настраиваете его на клиентском прокси (как и должны, они должны соответствовать привязкам), он будет взрываться в огненной смерти. Причиной этого является то, что указанная выше последовательность событий, которые WCF пытается предотвратить, такова:

  • клиентский поток 100MB файл на сервер анонимно в одном POST
  • сервер говорит, извините, 401, мне нужна аутентификация.
  • клиент снова передает файл 100 МБ на сервер с заголовком аутентификации
  • сервер принимает.

Обратите внимание, что вы только что отправили 200 МБ на сервер, когда вам нужно было отправить 100 МБ. Ну, это проблема. Ответ заключается в том, чтобы отправить аутентификацию с первой попытки, но это невозможно в WCF без написания пользовательского поведения. Во всяком случае, я отвлекаюсь.

Ваша проблема

Прежде всего, позвольте мне сказать вам, что то, что вы пытаетесь, невозможно 2. Теперь, чтобы вы перестали вращать свои колеса, позвольте мне сказать вам, почему:

Мне кажется, что вы теперь блуждаете в подобном классе проблем. Если вы включите защиту уровня сообщений, клиент должен загрузить весь поток данных в память, прежде чем он сможет фактически закрыть сообщение с помощью обычной хэш-функции и подписи xml, требуемой ws-security. Если он должен прочитать весь поток, чтобы подписать единственное сообщение (которое на самом деле не является сообщением, но оно представляет собой единственный непрерывный поток), вы можете увидеть проблему здесь. WCF должен будет передать его один раз "локально", чтобы вычислить безопасность сообщения, а затем передать его снова, чтобы отправить его на сервер. Это явно глупо, поэтому WCF не позволяет обеспечить безопасность сообщений для потоковой передачи данных.

Итак, простой ответ заключается в том, что вы должны отправить токен либо в качестве параметра в исходную веб-службу, либо в качестве заголовка SOAP, и использовать его для проверки. Вы не можете использовать WS-Security для этого. Честно говоря, это не просто проблема WCF - я не вижу, как она может практически работать для любых других стеков.

Решение проблемы с MTOM

Это просто пример того, как я решил проблему потоковой передачи MTOM для базовой проверки подлинности, поэтому, возможно, вы могли бы избавиться от этого и реализовать что-то подобное для вашей проблемы. Суть его в том, что для того, чтобы включить свой собственный инспектор сообщений, вам необходимо отключить все понятия безопасности на прокси-сервере клиента (он остается включенным на сервере), кроме уровня транспорта (SSL):

this._contentService.Endpoint.Behaviors.Add(
    new BasicAuthenticationBehavior(
        username: this.Settings.HttpUser,
        password: this.Settings.HttpPass));
var binding = (BasicHttpBinding)this._contentService.Endpoint.Binding;
binding.Security.Mode = BasicHttpSecurityMode.Transport; // SSL only            
binding.Security.Transport.ClientCredentialType = 
   HttpClientCredentialType.None; // Do not provide

Обратите внимание, что я отключил транспортную безопасность здесь, потому что я буду обеспечивать, чтобы я использовал инспектор сообщений и пользовательское поведение:

internal class BasicAuthenticationBehavior : IEndpointBehavior
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationBehavior(string username, string password)
    {
        this._username = username;
        this._password = password;
    }
    public void AddBindingParameters(ServiceEndpoint endpoint, 
        BindingParameterCollection bindingParameters) { }
    public void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
        var inspector = new BasicAuthenticationInspector(
            this._username, this._password);
        clientRuntime.MessageInspectors.Add(inspector);
    }
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
        EndpointDispatcher endpointDispatcher) { }
    public void Validate(ServiceEndpoint endpoint) { }
}

internal class BasicAuthenticationInspector : IClientMessageInspector
{
    private readonly string _username;
    private readonly string _password;

    public BasicAuthenticationInspector(string username, string password)
    {
        this._username = username;
        this._password = password;
    }

    public void AfterReceiveReply(ref Message reply,
        object correlationState) { }

    public object BeforeSendRequest(ref Message request,
        IClientChannel channel)
    {
        // we add the headers manually rather than using credentials 
        // due to proxying issues, and with the 101-continue http verb 
        var authInfo = Convert.ToBase64String(
            Encoding.Default.GetBytes(this._username + ":" + this._password));

        var messageProperty = new HttpRequestMessageProperty();
        messageProperty.Headers.Add("Authorization", "Basic " + authInfo);
        request.Properties[HttpRequestMessageProperty.Name] = messageProperty;

        return null;
    }
}

Итак, этот пример предназначен для всех, кто страдает проблемой MTOM, но также как скелет для реализации чего-то подобного для аутентификации вашего токена, сгенерированного первичной службой токенов, защищенной WIF.

Надеюсь, что это поможет.

(1) Большие данные и потоковое вещание

(2) Безопасность сообщений в WCF (см. "Недостатки".)