Есть сервис, предоставляющий wsdl, генерирую по нему ServiceModel.
Обмен по https, для аутентификации используется Bearer токен.
var timeout = new TimeSpan(0, 20, 0);
var address = new EndpointAddress(URL);
BasicHttpsBinding binding = new()
{
ReaderQuotas = XmlDictionaryReaderQuotas.Max,
MaxReceivedMessageSize = int.MaxValue,
SendTimeout = timeout,
ReceiveTimeout = timeout,
UseDefaultWebProxy = UseSystemProxy
};
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; // можно поставить Basic и заполнить UserName и Password, но это ничего не меняет
_client = new Client(binding, address);
(IContextChannel)_client.InnerChannel).OperationTimeout = timeout;
_client.Endpoint.AddCustomHeader("Bearer " + Token); // до IClientMessageInspector.BeforeSendRequest в данном случае не доходит
// где-то дальше
_client.Request();
// System.ServiceModel.Security.MessageSecurityException: 'Запрос HTTP не разрешен для схемы аутентификации клиента "Anonymous". От сервера получен заголовок аутентификации "Basic realm="Realm", Basic realm="Realm"".'
то попадаем в IClientMessageInspector.BeforeSendRequest и я устанавливаю в заголовок токен, но дальше сервер не отвечает и спустя минуту вылетает исключение
System.TimeoutException: 'Истекло время ожидания канала запроса при ожидании ответа по истечении 00:18:59.1452021. Увеличьте значение времени ожидания, передаваемое при вызове Request, или значение SendTimeout в Binding. Время, выделенное для выполнения этой операции, может быть составной частью более длительного времени ожидания.'
Непонятно откуда взялось "00:18:59.1452021"
В итоге не понятно что вообще происходит:
1. Почему, если я трогаю (IContextChannel)_client.InnerChannel).OperationTimeout, не обязательно сюда что-то писать (значение по умолчанию 10 минут), можно просто прочитать, то падает с System.ServiceModel.Security.MessageSecurityException: 'Запрос HTTP не разрешен для схемы аутентификации клиента "Anonymous". От сервера получен заголовок аутентификации "Basic realm="Realm", Basic realm="Realm"".'
2. Если не трогать OperationTimeout, то сервис не отвечет. Но можно сделать запрос из SoapUI и всё быстро отрабатывает. Что интересно, мой сервис тоже иногда может нормально отработать.
А что видно в трафике между клиентом и сервером? Я бы начал с запуска fiddlertool и посмотрел на сами запросы.
Уже потом, наверное, включил бы трассировку в дотнете и посмотрел, что там происходит.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>А что видно в трафике между клиентом и сервером? Я бы начал с запуска fiddlertool и посмотрел на сами запросы. S>Уже потом, наверное, включил бы трассировку в дотнете и посмотрел, что там происходит.
Смотрел через WireShark, но не разобрался как декодировать Data, там же https
Здравствуйте, binks, Вы писали:
B>Здравствуйте, Sinclair, Вы писали:
S>>А что видно в трафике между клиентом и сервером? Я бы начал с запуска fiddlertool и посмотрел на сами запросы. S>>Уже потом, наверное, включил бы трассировку в дотнете и посмотрел, что там происходит.
B>Смотрел через WireShark, но не разобрался как декодировать Data, там же https B>Image: sNyr94CidEb
Здравствуйте, binks, Вы писали: B>Смотрел через WireShark, но не разобрался как декодировать Data, там же https B>Image: sNyr94CidEb
Смотрите через fiddlertool, в него декодирование https встроено из коробки.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, binks, Вы писали:
B>Привет.
B>Есть сервис, предоставляющий wsdl, генерирую по нему ServiceModel.
B>Обмен по https, для аутентификации используется Bearer токен.
B>
B>var timeout = new TimeSpan(0, 20, 0);
B>var address = new EndpointAddress(URL);
B>BasicHttpsBinding binding = new()
B>{
B> ReaderQuotas = XmlDictionaryReaderQuotas.Max,
B> MaxReceivedMessageSize = int.MaxValue,
B> SendTimeout = timeout,
B> ReceiveTimeout = timeout,
B> UseDefaultWebProxy = UseSystemProxy
B>};
B>binding.Security.Mode = BasicHttpsSecurityMode.Transport;
B>binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; // можно поставить Basic и заполнить UserName и Password, но это ничего не меняет
B>_client = new Client(binding, address);
B>(IContextChannel)_client.InnerChannel).OperationTimeout = timeout;
B>_client.Endpoint.AddCustomHeader("Bearer " + Token); // до IClientMessageInspector.BeforeSendRequest в данном случае не доходит
B>// где-то дальше
B>_client.Request();
B>// System.ServiceModel.Security.MessageSecurityException: 'Запрос HTTP не разрешен для схемы аутентификации клиента "Anonymous". От сервера получен заголовок аутентификации "Basic realm="Realm", Basic realm="Realm"".'
B>
B>то попадаем в IClientMessageInspector.BeforeSendRequest и я устанавливаю в заголовок токен, но дальше сервер не отвечает и спустя минуту вылетает исключение B>
B>System.TimeoutException: 'Истекло время ожидания канала запроса при ожидании ответа по истечении 00:18:59.1452021. Увеличьте значение времени ожидания, передаваемое при вызове Request, или значение SendTimeout в Binding. Время, выделенное для выполнения этой операции, может быть составной частью более длительного времени ожидания.'
B>
Это же кажется WCF? Если да, тогда советую выкрутить все логи по максимуму.
Здравствуйте, binks, Вы писали:
B>_client.Endpoint.AddCustomHeader("Bearer " + Token); // до IClientMessageInspector.BeforeSendRequest в данном случае не доходит
Я не очень понял что это за API, но я нигде не вижу имени хедера. Bearer должен выглядеть так:
Authorization: Bearer <token>
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, binks, Вы писали:
B>>_client.Endpoint.AddCustomHeader("Bearer " + Token); // до IClientMessageInspector.BeforeSendRequest в данном случае не доходит ·>Я не очень понял что это за API, но я нигде не вижу имени хедера. Bearer должен выглядеть так: ·>
·>Authorization: Bearer <token>
·>
Я не обратил внимание, что тут криво сделано. Там внутри как раз заполняется заголовок Authorization.
Это SOAP API
Очень странно, но я не нашёл другого способа добавить авторизацию с bearer токеном, кроме как через интерфейс интерцептера.
За давностью лет мог уже многое позабыть, так что отвечу как и что вспомню.... B>Непонятно откуда взялось "00:18:59.1452021"
Ну т.к. это ~19 минут, а вы сказали, что ошибку получаете через примерно минуту, то логично что это выставленный вами timout == 20 минут, минус минута ожидания.
Тут, конечно сообщения с такой формулировкой, что кого угодно собьет с толку... Но в целом получается так:
— вы в биндинге указываете SendTimeout и ReceiveTimeout по 20 минут. Это время на выполнение целиком операции — с того момента, как вы начали запрос, до получения ответа.
— однако у вас всё обрывается уже через минуту. Это, как я понимаю, таймаут транспорта. Т.е. ваш нижележащий уровень не смог за минуту установить соединение и вернул ошибку.
Ну и в результате у вас осталось еще 19 минут на операцию, но вы получили таймаут от канального уровня.
B>1. Почему, если я трогаю (IContextChannel)_client.InnerChannel).OperationTimeout, не обязательно сюда что-то писать (значение по умолчанию 10 минут), можно просто прочитать, то падает с System.ServiceModel.Security.MessageSecurityException: 'Запрос HTTP не разрешен для схемы аутентификации клиента "Anonymous". От сервера получен заголовок аутентификации "Basic realm="Realm", Basic realm="Realm"".'
Потому что у WCF конструирование Pipeline, т.е. всего — от канала до прикладного уровня происходит, обычно, когда вы только делаете первый запрос (и после этого он становится read-only и там почти ничего менять уже нельзя)
Причем, и все проверки на то, на сколько вы указали совместимые между собой параметры происходят только в этот момент (ну не совсем так — что-то и прямо в момент создания, например, биндинга проверится — но там самый минимум... типа не передали ли вы пустой адрес или что-то в этом роде).
Но в некоторых случаях конструирование, а значит и проверки, можно вызвать, если обратиться к некоторым внутренним структурам. Ну примерно как у вас. Вы обращаетесь к внутреннему каналу, а чтобы его создать, нужно вызвать Binder, который уже начнет всё проверять... Ну вывалит вам ошибку.
Теперь о самой ошибке.
Там вроде тоже всё прозрачно. Вы говорите, что у вас безопасность на уровне транспорта (binding.Security.Mode = BasicHttpsSecurityMode.Transport), но аутентификации нет (binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None).
Это недопустимая комбинация.
Более того, вы писали в комментарии, что установка HttpClientCredentialType.Basic ничего не меняет. Логично, если вы хотите безопасность на уровне транспорта, нужно работать по HTTPS, других вариантов WCF знать не хочет. Проверьте, что вы в качестве Endpoint указываете схему с https. Если явно не указываете — проверьте базовый адрес.
Наверное, самое простое, хоть и костыльное, решение, будет:
— выключить безопасность вообще (None) или оставить TransportWithMessageCredential (но тогда проверить, что работает по HTTPS).
— прописать свой Authorization заголовок. Например, вот так https://stackoverflow.com/questions/964433/how-to-add-a-custom-http-header-to-every-wcf-call (тот пример, что у вас — это добавление заголовка к SOAP сообщению, это не то.
Здравствуйте, Михаил Романов, Вы писали:
МР>Ну и в результате у вас осталось еще 19 минут на операцию, но вы получили таймаут от канального уровня.
Похоже на правду.
МР>Но в некоторых случаях конструирование, а значит и проверки, можно вызвать, если обратиться к некоторым внутренним структурам. Ну примерно как у вас. Вы обращаетесь к внутреннему каналу, а чтобы его создать, нужно вызвать Binder, который уже начнет всё проверять... Ну вывалит вам ошибку.
Похоже на правду.
МР>Теперь о самой ошибке. МР>Там вроде тоже всё прозрачно. Вы говорите, что у вас безопасность на уровне транспорта (bind
ing.Security.Mode = BasicHttpsSecurityMode.Transport), но аутентификации нет (binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None). МР>Это недопустимая комбинация.
Почему недопустимая?
Я так понимаю, что безопасность на уровне транспорта это ssl. А будет ли доступ к ресурсам регулироваться паролем/сертификатом или ещё чем-то — не важно.
МР>Более того, вы писали в комментарии, что установка HttpClientCredentialType.Basic ничего не меняет. Логично, если вы хотите безопасность на уровне транспорта, нужно работать по HTTPS, других вариантов WCF знать не хочет. Проверьте, что вы в качестве Endpoint указываете схему с https. Если явно не указываете — проверьте базовый адрес.
Не меняется, если я трогаю InnerChannel.
МР>Вообще вы хотите не очень подходящий для WCF сценарий — свою кастомную аутентификацию на уровне транспорта. МР>Посмотрите, вот тут https://learn.microsoft.com/en-us/archive/blogs/carlosfigueira/wcf-extensibility может быть что-то вам подойдет. МР>Наверное, самое простое, хоть и костыльное, решение, будет: МР>- выключить безопасность вообще (None) или оставить TransportWithMessageCredential (но тогда проверить, что работает по HTTPS). МР>- прописать свой Authorization заголовок. Например, вот так https://stackoverflow.com/questions/964433/how-to-add-a-custom-http-header-to-every-wcf-call (тот пример, что у вас — это добавление заголовка к SOAP сообщению, это не то.
Добавление заголовка в soap происходит именно при TransportWithMessageCredential, потому-что получаю ошибку
System.ServiceModel.Security.MessageSecurityException: 'Незащищенное или неправильно защищенное сообщение об ошибке было получено от другой стороны. Код ошибки и описание см. внутреннее исключение.'
Inner Exception: One or more mandatory SOAP header blocks not understood
Склоняюсь к тому, что это либо что-то внтурях WCF, либо ошибка сервера. Обмен какой-то идёт, но как его расшифровать не ясно. Да трассировку WCF включить не получается.