简体   繁体   中英

Windows authentication with SOAP service and IIS failing

I have a client that sends a simple web request to a SOAP service. It is a simple C# program that uses the WSDL file of the service to create a client. The service is hosted on IIS 8.5 and Windows Server 2012. It works fine when using anonymous authentication but it fails with Windows authentication. Both client and service are in the same domain, user permissions are also fine.

I configured IIS so that it disables all forms of authentication except Windows authentication (Negotiate, NTLM). The client is configured so that it uses Windows as the client credential type.

When I send a request I get the following error: "The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate, NTLM'"

I then tried out a tool I found on github called "WebServiceStudio" . With that tool I set the WSDL, selected my request method and it worked, even with Windows authentication.

I looked at both attempts with Wireshark and noticed that the WebServiceStudio request immediately sends the Negotiate token with the first request while my own client sends the token in the second request, which to my understanding is how Windows authentication usually works.

I tried on IIS side but nothing worked so far:

  • Changed authentication order (Negotiate, NTLM and NTLM, Negotiate)
  • Changed authentication to only Negotiate
  • Changed extended protection in the advanced settings (neither option made a difference)
  • Verified that the WindowsAuthentication and WindowsAuthenticationModule were both installed

My goal is that my own C# client can successfully authenticate with Windows authentication.

Here's the C# client's configuration:

<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <system.serviceModel>
    <client>
      <endpoint address="server address" binding="basicHttpBinding"
          bindingConfiguration="MyContractSoap" contract="MyContract.MyContractSoap" />
    </client>
    <bindings>
      <basicHttpBinding>
        <binding name="MyContractSoap">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="TransportCredentialOnly">
            <transport clientCredentialType="Windows" proxyCredentialType="Windows" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
  </system.serviceModel>
</configuration>

And here is the wireshark data of my client's request:

POST /ABC/ShipmentDocuments.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "ABC/DocumentShipped"
Host: sdespte3
Content-Length: 333
Expect: 100-continue
Accept-Encoding: gzip, deflate

<!-- Server rejects request and states authentication method -->
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
Date: Tue, 18 Feb 2020 10:20:01 GMT
Content-Length: 1344

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Nicht autorisiert: Zugriff aufgrund ung.ltiger Anmeldeinformationen verweigert.</title>
</head>
<body>
<div id="header"><h1>Serverfehler</h1></div>
<div id="content">
 <div class="content-container"><fieldset>
  <h2>401 - Nicht autorisiert: Zugriff aufgrund ung.ltiger Anmeldeinformationen verweigert.</h2>
  <h3>Die angegebenen Anmeldeinformationen berechtigen Sie nicht, dieses Verzeichnis oder diese Seite anzuzeigen.</h3>
 </fieldset></div>
</div>
</body>
</html>

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>Request data here</s:Body></s:Envelope>

<!-- We send the negotiate token -->
POST /ABC/ShipmentDocuments.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "ABC/DocumentShipped"
Accept-Encoding: gzip, deflate
Authorization: Negotiate YIIHog...Token here
Host: abc
Content-Length: 333
Expect: 100-continue

<!-- Rejected again, unsure why -->
HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
Date: Tue, 18 Feb 2020 10:20:01 GMT
Content-Length: 1344

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<title>401 - Nicht autorisiert: Zugriff aufgrund ung.ltiger Anmeldeinformationen verweigert.</title>
<style type="text/css">

And finally the wireshark data of the other tool that worked:

POST /ABC/ShipmentDocuments.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "ABC/DocumentShipped"
Authorization: Negotiate YIILV...Token here
Host: sdespiis1
Content-Length: 415
Expect: 100-continue

HTTP/1.1 100 Continue

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body>Request body here</soap:Body></soap:Envelope>

<!-- Accepted -->
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/8.5
X-AspNet-Version: 4.0.30319
Persistent-Auth: false
X-Powered-By: ASP.NET
WWW-Authenticate: Negotiate oYG2MIGzo... Token here
Date: Tue, 18 Feb 2020 15:24:39 GMT
Content-Length: 295

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body>Body here</soap:Body></soap:Envelope>

Update: Here is the client's source code to call the service.

Program:

class Program
    {
        static void Main(string[] args)
        {
            sendWebRequest();
        }


        static int _orderId = 1;
        static int _mandant = 1;
        static string _sId = "0123456789012345678901";
        static string _isShipped = "eingeliefert";

        static void sendWebRequest()
        {
            Console.WriteLine("Start webrequest Orderid: {0}, mandant: {1}, sId: {2}, isShipped: {3}", _orderId, _mandant, _sId, _isShipped);
            WebserviceManager wm = new WebserviceManager();
            wm.Open();
            wm.SetStateToShipped(_orderId, _mandant, _sId, _isShipped);
            wm.Close();
            Console.WriteLine("Webrequest erfolgreich");
        }
    }

WebserviceManager:

public class WebserviceManager
    {
        protected MyContract.MyContractSoapClient _soapClient;

        public WebserviceManager()
        {
        }

        public void Open() 
        {
            _soapClient = createWebServiceClient();
            try
            {
                _soapClient.Open();
            }
            catch (Exception ex)
            {
                Logging.Error("Open", ex);
                throw ex;
            }

            Logging.Info("_soap-Client open");

        }

        public void Close()
        {
            _soapClient.Close();
        }

        public void SetStateToShipped(int orderNo, int mandant, string sId, string isShipped)
        {
            _soapClient.DocumentShipped(orderNo, mandant, sId, isShipped);
        }

        protected MyContract.MyContractSoapClient createWebServiceClient()
        {            
            return new MyContract.MyContractSoapSoapClient();
        }
    }

So it looks like the impersonation was not properly set up. I added the following line in my client program, right after creating the client object:

protected MyContract.MyContractSoapClient createWebServiceClient()
{            
    var client = new MyContract.MyContractSoapSoapClient();
    client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;

    return client;
}

And now Windows authentication works as expected!

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