Я пытаюсь подключиться к REST-службе HttpClient
с помощью HttpClient
с использованием HttpClient
аутентификации, но получаю (401) неавторизованным. Когда я получаю доступ к той же конечной точке из веб-браузера, ввод одного и того же имени пользователя и пароля приводит к успеху. Аутентификация на стороне сервиса выполняется через UserNamePasswordValidator
. Я оставляю метод Validate
пустым для целей тестирования, поэтому все запросы должны быть действительными. Тем не менее, вызов GetAsync()
приводит к (401) несанкционированному. Когда я устанавливаю точку останова в методе Validate
, я могу проверить, что переданы правильные значения. Есть ли объяснение этому поведению?
Клиент
using (var httpClient = new HttpClient())
{
var authString = "admin:admin";
var authEncoded = Encoding.GetEncoding("ISO-8859-1").GetBytes(authString);
var authBase64String = Convert.ToBase64String(authEncoded);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authBase64String);
httpClient.BaseAddress = new Uri(UriFactory.GetServiceUrl());
using (var response = await httpClient.GetAsync(serviceDomain))
{
string responseData = await response.Content.ReadAsStringAsync();
return JsonConverter.FromJson<TResponse>(responseData);
}
}
Сервис
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
}
}
Это конфигурация сервиса
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="HttpsBinding">
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="MyServiceBeahvior" name="ServiceImplementation">
<endpoint address="status" binding="webHttpBinding" bindingConfiguration="HttpsBinding" contract="Status.IStatusService" behaviorConfiguration="MyWebBahviorr"/>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
<host>
<baseAddresses />
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBeahvior">
<serviceMetadata httpsGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyWebBahvior">
<webHttp automaticFormatSelectionEnabled="false" />
</behavior>
</endpointBehaviors>
</behaviors>
_oServiceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
_oServiceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
Связь по HTTPS с самозаверяющим сертификатом.
Наконец, я смог найти свою проблему с помощью Fiddler. Я отправлял запрос без последней косой черты (например, https://localhost: 4444/status вместо https://localhost: 4444/status/). Веб-браузер смог обработать перенаправление, но HttpClient не удалось.
Прежде всего следует отметить, что CustomUsernamePasswordValidator доступен только при наличии учетных данных клиента - UserName.
<bindings>
<wsHttpBinding>
<binding name="wsbd">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="svbhv">
<serviceAuthorization principalPermissionMode="Custom">
<authorizationPolicies>
<add policyType="Server7.CustAuthorPolicy,Server7"/>
</authorizationPolicies>
</serviceAuthorization>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Server7.MyCustUserNamePassValidator,Server7"/>
</serviceCredentials>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
Поэтому учетными данными по умолчанию должны быть учетные данные Windows на сервере. По умолчанию для самозаверяющих сертификатов существует процесс проверки, поэтому необходимо добавить обратный вызов проверки в сертификат сервера.
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
Я сделал демо, желаю, чтобы это было полезно для вас. Серверная сторона.
public interface IService1
{
[OperationContract]
[WebGet]
string GetData(int value);
}
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
Config.
<system.serviceModel>
<services>
<service name="WcfService3.Service1">
<endpoint address="" binding="webHttpBinding" contract="WcfService3.IService1" bindingConfiguration="mybinding" behaviorConfiguration="rest"></endpoint>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"></endpoint>
</service>
</services>
<bindings>
<webHttpBinding>
<binding name="mybinding">
<security mode="Transport">
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="rest">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
Сторона клиента.
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
using (var httpClient = new HttpClient())
{
var authString = "administrator:abcd1234!";
var authEncoded = Encoding.UTF8.GetBytes(authString);
var authBase64String = Convert.ToBase64String(authEncoded);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",authBase64String);
var response = httpClient.GetAsync("https://127.0.0.1:8863/Service1.svc/getdata?value=100");
string responsedata = response.Result.Content.ReadAsStringAsync().Result;
XmlDocument doc = new XmlDocument();
doc.LoadXml(responsedata);
Console.WriteLine(doc.InnerText);
}
Результат.