简体   繁体   中英

Consuming a WCF REST service with Basic authentication using HttpClient.GetAsync() results in (401) Unauthorized

I am trying to connect to a WCF self-hosted REST service with HttpClient using Basic authentication, but keep getting (401) unauthorized. When I access the same endpoint from a web browser, entering the same user name and password results in success. The authentication on service side is done via UserNamePasswordValidator . I am leaving the Validate method empty for testing purposes, so all the requests should be valid. Nonetheless calling GetAsync() results in (401) Unauthorized. When I set a breakpoint in the Validate method, I can check, that correct values are passed. Any explanation for this behavior?

The client

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);
    }
}

The service

public class CustomUserNameValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
    }
}

This is the service configuration

<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();

The communication is over HTTPS with a self-signed certificate.

Finally, I was able to find my problem using Fiddler. I was sending the request without the final slash (eg https://localhost:4444/status instead of https://localhost:4444/status/ ). The web browser was able to handle the redirect, but the HttpClient failed.

First thing must be noted that CustomUsernamePasswordValidator is only available that the client credential is 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>

So the default credential should be the server windows credential. By default, there is a validation process for self-signed certificates, so you need to add a validation callback to the server certificate

ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

I have made a demo, wish it is useful to you. Server side.

    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>

Client side.

  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);
    }

Result.
在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM