使用 HttpClient.GetAsync() 使用具有基本身份验证的 WCF REST 服务会导致 (401) Unauthorized

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

我正在尝试使用基本身份验证 HttpClient 连接到 WCF 自托管 REST 服务,但一直收到 (401) 未授权。当我从网络浏览器访问同一个端点时,输入相同的用户名和密码会成功。服务端的身份验证是通过 UserNamePasswordValidator 完成的。出于测试目的,我将 Validate 方法留空,因此所有请求都应该有效。尽管如此,调用 GetAsync() 会导致 (401) Unauthorized。当我在 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 instead of https://localhost:4444/status/)。 Web 浏览器能够处理重定向,但 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;

我做了一个demo,希望对你有用。 服务器端。

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

配置.

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

结果。