WCF Rest 自托管证书安全服务返回 401 未经授权

Posted

技术标签:

【中文标题】WCF Rest 自托管证书安全服务返回 401 未经授权【英文标题】:WCF Rest self hosted certificate secured service returns 401 unauthorized 【发布时间】:2020-04-17 22:30:18 【问题描述】:

我想创建一个 WCF 控制台自托管 - 带有证书的服务器端身份验证 - 休息服务。

我在实际调用服务时遇到了问题,因为我总是收到 401 Unauthorized 响应。

由于这是一种“单向”身份验证,其中服务向客户端标识自己,为什么我总是会作为客户端应用程序获得 401 未经授权的响应(好像客户端需要向服务标识自己以访问它的资源?)

谁能帮我查明我哪里出错了,以及如何让我的客户服务通信最终正常工作?

简单服务合同:

[ServiceContract]
public interface IService1


    [OperationContract]
    [WebGet(UriTemplate = "Students", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    List<Student> GetStudentDetails();

    // TODO: Add GetMethod with parameter 
    [OperationContract]
    [WebGet(UriTemplate = "Student/id", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    Student GetStudentWithId(string id);

    //TODO: add one post method here
    [OperationContract]
    [WebInvoke(Method="POST", UriTemplate = "Student/New", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    void NewStudent(Stream stream);

    //TODO: add one post method here
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "Student/NewS", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
    void NewStudentS(Student stream);


// Use a data contract as illustrated in the sample below to add composite types to service operations.
[DataContract]
public class Student

    [DataMember]
    public int ID
    
        get;
        set;
    

    [DataMember]
    public string Name
    
        get;
        set;
    

服务实现:

public class Service1 : IService1

    public List<Student> GetStudentDetails()
    
        return new List<Student>()  new Student()  ID = 1, Name = "Goran"  ;
    

    public Student GetStudentWithId(string id)
    
        return new Student()  ID = Int32.Parse(id), Name = "Ticbra RanGo" ;
    

    public void NewStudent(Stream stream)
    
        using(stream)
        
            // convert Stream Data to StreamReader
            StreamReader reader = new StreamReader(stream);
            var dataString = reader.ReadToEnd();

            Console.WriteLine(dataString);
        
    

    public void NewStudentS(Student student)
    
        Console.WriteLine(student.Name);
    

我的控制台应用程序运行该服务:

static void Main(string[] args)
    
        Uri httpUrl = new Uri("https://localhost:8080/TestService");
        using (WebServiceHost host = new WebServiceHost(typeof(Service1)))
        
             // Create the binding.  
            WSHttpBinding binding = new WSHttpBinding();
            binding.Name = "binding1";
            binding.Security.Mode = SecurityMode.Transport;


            host.AddServiceEndpoint(typeof(IService1), binding, httpUrl/*"rest"*/);
            // Enable metadata publishing.
            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            host.Description.Behaviors.Add(smb);              

            //Add host certificate to service wcf for identification
            host.Credentials.ServiceCertificate.SetCertificate(
                StoreLocation.LocalMachine,
                StoreName.My,
                X509FindType.FindBySubjectName,
                "localhost");

            host.Open();

            foreach (ServiceEndpoint se in host.Description.Endpoints)
                Console.WriteLine("Service is host with endpoint " + se.Address);

            Console.WriteLine("Host is running... Press < Enter > key to stop");
            Console.ReadLine();
            host.Close();
        

        //Console.WriteLine("ASP.Net : " + ServiceHostingEnvironment.AspNetCompatibilityEnabled);
        Console.WriteLine("Host is running... Press < Enter > key to stop");
        Console.ReadLine();
    

请注意,我通过 KeyStore Explorer 创建了证书根和子证书,并将它们适当地放置在 Windows 上的个人和受信任的根证书中。

我通过 CMD 将服务器证书映射到端口 8080。

我使用的客户端是 SOAPUI,以及我的手动编码客户端。 客户代码:

        WebRequest request = HttpWebRequest.Create(urlTextBox.Text);

        var webResponse = request.GetResponse();

        using (Stream dataStream = webResponse.GetResponseStream())
        
            // Open the stream using a StreamReader for easy access.  
            StreamReader reader = new StreamReader(dataStream);
            // Read the content.  
            string responseFromServer = reader.ReadToEnd();
            // Display the content.  
            Console.WriteLine(responseFromServer);
            HttpResonseTextBox.Text = responseFromServer;
        

最好的问候,并在此先感谢您

【问题讨论】:

【参考方案1】:

WSHttpBinding 绑定 = new WSHttpBinding(); binding.Name = "binding1"; binding.Security.Mode = SecurityMode.Transport;

以上代码以windows认证为客户端认证方式。

        //this is the default value unless we specify it manually.
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;

因此,我们应该通过提供 Windows 凭据在客户端调用它。 此外,这种 WCF 服务不称为 Rest API,它称为 SOAP Web 服务。我们通常使用客户端代理来调用它。https://docs.microsoft.com/en-us/dotnet/framework/wcf/accessing-services-using-a-wcf-client 然后设置windows凭据并调用该方法。

            ServiceReference1.ServiceClient client = new ServiceReference1.ServiceClient();
            //these are windows accounts on the server-side.
            client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
            client.ClientCredentials.Windows.ClientCredential.Password = "123456";
            var result = client.Test();
            Console.WriteLine(result);

如果我们要创建一个rest服务,请使用Webhttpbinding创建服务。

            WebHttpBinding binding = new WebHttpBinding();
            binding.Security.Mode = WebHttpSecurityMode.Transport;
            //this is default value. 
            binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

同样,我们需要将证书绑定到特定端口,以使传输层安全可用。

netsh http add sslcert ipport=0.0.0.0:8000 certash=0000000000003ed9cd0c315bbb6dc1c08da5e6 appid=00112233-4455-6677-8899-AABBCCDDEEFF

Netsh Http 命令。https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-configure-a-port-with-an-ssl-certificate 因此,验证客户端的安全模式为HttpClientCredentialType.None。我们不需要在客户端提供 Windows 凭据。 如果有什么可以帮助的,请随时告诉我。

【讨论】:

嗨,亚伯拉罕。感谢您的详细回复。似乎问题出在我使用的绑定类型错误并且未将 ClientCredentialType 设置为“无”。非常感谢您的帮助!【参考方案2】:

您将证书映射到端口 8080 以使 https 协议正常工作。 到现在为止都很好。

但是 401 错误意味着服务需要来自客户端的一些凭据(如果有,则会引发 401 错误)

请尝试删除(或评论)SetCertificate 方法调用,如下所示

           //Add host certificate to service wcf for identification
            //host.Credentials.ServiceCertificate.SetCertificate(
            //    StoreLocation.LocalMachine,
            //    StoreName.My,
            //    X509FindType.FindBySubjectName,
            //    "localhost");

并尝试它是否有效。只是为了检查

带有证书的 Wcf 传输安全要求客户端也指定证书like it is documented。

我不确定您是否可以使用带有HttpWebRequest 的soap 协议使用带有证书身份验证的wcf 服务。它需要使用带有SetCertificate 方法的wcf 客户端:

// The client must specify a certificate trusted by the server.  
cc.ClientCredentials.ClientCertificate.SetCertificate(  
    StoreLocation.CurrentUser,  
    StoreName.My,  
    X509FindType.FindBySubjectName,  
    "contoso.com");  

(这是文档中的示例)

【讨论】:

以上是关于WCF Rest 自托管证书安全服务返回 401 未经授权的主要内容,如果未能解决你的问题,请参考以下文章

WCF 自托管命令行返回 503 错误

对于 WCF 服务,没有证书的 TransportWithMessageCredential 是不是足够安全?

通过 HTTPS 对 WCF 自托管 REST 服务的 POST 请求示例

自托管 WCF REST 服务 JSON POST 方法不允许

如何以编程方式为 WCF 服务创建自签名证书?

我可以在我的自托管 WCF 应用程序中使用 Let's Encrypt 证书吗?