【问题标题】:ONVIF wsdl service: unable to authenticateONVIF wsdl 服务:无法验证
【发布时间】:2017-09-02 11:46:06
【问题描述】:

我正在使用 .NET 4(Windows 窗体,而不是 WCF)开发 ONVIF 驱动程序。 我开始在 Visual Studio 中将 WSDL 文件作为服务导入。 所以我可以通过这种方式向设备发送命令:

HttpTransportBindingElement httpTransportBindingElement = new HttpTransportBindingElement();
[...]

TextMessageEncodingBindingElement messegeElement = new TextMessageEncodingBindingElement();
[...]
CustomBinding binding = new CustomBinding(messegeElement, httpTransportBindingElement);
[...]

EndpointAddress serviceAddress = new EndpointAddress(url);

DeviceClient deviceClient = new DeviceClient(binding, serviceAddress);

Device channel = deviceClient.ChannelFactory.CreateChannel();

DeviceServiceCapabilities dsc = channel.GetServiceCapabilities();

但我无法管理 HTTP 摘要身份验证。我花了几天时间搜索谷歌示例和解决方案,但唯一的方法似乎是手写 XML 代码。没有任何干净的解决方案,例如:

deviceClient.ChannelFactory.Credentials.HttpDigest.ClientCredential.UserName = USERNAME;
deviceClient.ChannelFactory.Credentials.HttpDigest.ClientCredential.Password = digestPassword;

(这不起作用)?

【问题讨论】:

    标签: c# authentication wsdl digest onvif


    【解决方案1】:

    首先你应该安装 Microsoft.Web.Services3 包。 (查看> 其他窗口> 包管理器控制台)。然后,您必须将摘要行为添加到您的端点。代码的第一部分是 PasswordDigestBehavior 类,之后用于连接 ONVIF 设备服务。

    public class PasswordDigestBehavior : IEndpointBehavior
    {
        public String Username { get; set; }
        public String Password { get; set; }
    
        public PasswordDigestBehavior(String username, String password)
        {
            this.Username = username;
            this.Password = password;
        }
    
    
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
            // do nothing
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {
            //clientRuntime.MessageInspectors.Add(new PasswordDigestMessageInspector(this.Username, this.Password));
            clientRuntime.MessageInspectors.Add(new PasswordDigestMessageInspector(this.Username, this.Password));
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            throw new NotImplementedException();
        }
    
        public void Validate(ServiceEndpoint endpoint)
        {
            // do nothing...
        }
    }
    
    
    public class PasswordDigestMessageInspector : IClientMessageInspector
    {
        public String Username { get; set; }
        public String Password { get; set; }
    
        public PasswordDigestMessageInspector(String username, String password)
        {
            this.Username = username;
            this.Password = password;
        }
    
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            // do nothing
        }
    
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            // Use the WSE 3.0 security token class
            var option = PasswordOption.SendHashed;
            if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
                option = PasswordOption.SendPlainText;
    
            UsernameToken token = new UsernameToken(this.Username, this.Password, option);
    
            // Serialize the token to XML
            XmlDocument xmlDoc = new XmlDocument();
            XmlElement securityToken = token.GetXml(xmlDoc);
    
            // find nonce and add EncodingType attribute for BSP compliance
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(xmlDoc.NameTable);
            nsMgr.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
            XmlNodeList nonces = securityToken.SelectNodes("//wsse:Nonce", nsMgr);
            XmlAttribute encodingAttr = xmlDoc.CreateAttribute("EncodingType");
            encodingAttr.Value = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary";
            if (nonces.Count > 0)
            {
                nonces[0].Attributes.Append(encodingAttr);
                //nonces[0].Attributes[0].Value = "foo";
            }
    
    
            //
            MessageHeader securityHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", securityToken, false);
            request.Headers.Add(securityHeader);
    
            // complete
            return Convert.DBNull;
        }
    }
    

    这是如何使用它:

    var endPointAddress = new EndpointAddress("http://DEVICE_IPADDRESS/onvif/device_service");
                var httpTransportBinding = new HttpTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Digest };
                var textMessageEncodingBinding = new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None) };
                var customBinding = new CustomBinding(textMessageEncodingBinding, httpTransportBinding);
                var passwordDigestBehavior = new PasswordDigestBehavior(USERNAME, PASSWORD);
                var deviceService = new DeviceClient(customBinding, endPointAddress);
                deviceService.Endpoint.Behaviors.Add(passwordDigestBehavior);
    

    【讨论】:

    • 您的解决方案正在运行,但正在执行 SOAP 身份验证而不是 HTTP Digest 身份验证。无论如何,最终我能够在不使用 WSE 3.0 的情况下执行这两种类型的身份验证。
    【解决方案2】:

    对于未来的读者,我终于能够在不使用 WSE 3.0 的情况下执行这两种类型的身份验证。 这是部分代码(为简短起见),基于 IClientMessageInspector 接口(您可以找到很多基于此接口的其他示例):

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            if (HTTPDigestAuthentication)
            {
                string digestHeader = string.Format("Digest username=\"{0}\",realm=\"{1}\",nonce=\"{2}\",uri=\"{3}\"," +
                                                    "cnonce=\"{4}\",nc={5:00000000},qop={6},response=\"{7}\",opaque=\"{8}\"",
                                                    _username, realm, nonce, new Uri(this.URI).AbsolutePath, cnonce, counter, qop, digestResponse, opaque);
    
                HttpRequestMessageProperty httpRequest = new HttpRequestMessageProperty();
                httpRequest.Headers.Add("Authorization", digestHeader);
                request.Properties.Add(HttpRequestMessageProperty.Name, httpRequest);
    
                return Convert.DBNull;
            }
            else if (UsernametokenAuthorization)
            {
                string headerText = "<wsse:UsernameToken xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\" xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" +
                                    "<wsse:Username>" + _username + "</wsse:Username>" +
                                    "<wsse:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest\">" + digestPassword + "</wsse:Password>" +
                                    "<wsse:Nonce EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary\">" + Convert.ToBase64String(nonce) + "</wsse:Nonce>" +
                                    "<wsu:Created xmlns=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\">" + created + "</wsu:Created>" +
                                    "</wsse:UsernameToken>";
    
                XmlDocument MyDoc = new XmlDocument();
                MyDoc.LoadXml(headerText);
    
                MessageHeader myHeader = MessageHeader.CreateHeader("Security", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", MyDoc.DocumentElement, false);
    
                request.Headers.Add(myHeader);
    
                return Convert.DBNull;
            }
    
            return request;
        }
    

    【讨论】:

    • 您的示例中缺少很多东西,还有一些非常重要的东西......您如何获得用于格式化摘要标题的变量? (例如nonce?你需要为此提出请求吗?)
    • 是的,你是对的:你必须发送一个未经身份验证的请求,服务器将回答一个包含 nonce、realm 等的错误消息。
    • 你是在BeforeSendRequest的开头做自己的HttpClient吗?您能否编辑您的答案并提供更完整的示例?
    • 查看此链接以获取更完整的示例以从以下位置开始:findnerd.com/list/view/…
    • 是的,我发现网上到处都是关于使用 usernametoken 机制的例子。我正在使用它,它工作得很好。但是,我对您的“HttpDigest”机制感到好奇。 Onvif 的规范现在声明“本标准中定义的服务应根据 [RFC 2617] 使用摘要身份验证进行保护,但支持 [WS-UsernameToken] 的旧设备除外。”这让我觉得 WS-UsernameToken 已成为过去(对于 ONVIF)。此外,您是我在互联网上找到的唯一一个不用于 AD 的带有服务参考的 http 摘要身份验证示例。
    【解决方案3】:

    这个类应该能够替换 WSE UserNameToken 对象并移除对 WSE 的依赖。它还使得在 IClientInspector 中搜索和修复 nonce 变得不必要。我只在 1 台相机上测试过,而且只使用散列密码。 YMMV。

    public enum PasswordOption
    {
        SendPlain = 0,
        SendHashed = 1,
        SendNone = 2
    }
    
    public class UsernameToken
    {
        private string Username;
        private string Password;
        private PasswordOption PwdOption;
    
        public UsernameToken(string username, string password, PasswordOption option)
        {
            Username = username;
            Password = password;
            PwdOption = option;
        }
    
    
        public XmlElement GetXml(XmlDocument xmlDoc)
        {
            string wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
            string wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
            XmlDocument doc = xmlDoc;
            //XmlElement securityEl = doc.CreateElement("Security", wsse);
    
            XmlElement usernameTokenEl = doc.CreateElement("wsse", "UsernameToken", wsse);
            XmlAttribute a = doc.CreateAttribute("wsu", "Id", wsu);
            usernameTokenEl.SetAttribute("xmlns:wsse", wsse);
            usernameTokenEl.SetAttribute("xmlns:wsu", wsu);
            a.InnerText = "SecurityToken-" + Guid.NewGuid().ToString();
            usernameTokenEl.Attributes.Append(a);
    
            //Username
            XmlElement usernameEl = doc.CreateElement("wsse:Username", wsse);
            usernameEl.InnerText = Username;
            usernameTokenEl.AppendChild(usernameEl);
    
            //Password
            XmlElement pwdEl = doc.CreateElement("wsse:Password", wsse);
    
    
            switch (PwdOption)
                {
                case PasswordOption.SendHashed:
                    //Nonce+Create+Password
                    pwdEl.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
                    string created = DateTime.Now.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ");
                    byte[] nonce = GenerateNonce(16);
                    byte[] pwdBytes = Encoding.ASCII.GetBytes(Password);
                    byte[] createdBytes = Encoding.ASCII.GetBytes(created);
                    byte[] pwdDigest = new byte[nonce.Length + pwdBytes.Length + createdBytes.Length];
                    Array.Copy(nonce, pwdDigest, nonce.Length);
                    Array.Copy(createdBytes, 0, pwdDigest, nonce.Length, createdBytes.Length);
                    Array.Copy(pwdBytes, 0, pwdDigest, nonce.Length + createdBytes.Length, pwdBytes.Length);
                    pwdEl.InnerText = ToBase64(SHA1Hash(pwdDigest));
                    usernameTokenEl.AppendChild(pwdEl);
    
                    //Nonce
                    XmlElement nonceEl = doc.CreateElement("wsse:Nonce", wsse);
                    nonceEl.SetAttribute("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
                    nonceEl.InnerText = ToBase64(nonce);
                    usernameTokenEl.AppendChild(nonceEl);
    
                    //Created
                    XmlElement createdEl = doc.CreateElement("wsu:Created", wsu);
                    createdEl.InnerText = created;
                    usernameTokenEl.AppendChild(createdEl);
                    break;
                case PasswordOption.SendNone:
                    pwdEl.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
                    pwdEl.InnerText = "";
                    usernameTokenEl.AppendChild(pwdEl);
                    break;
                case PasswordOption.SendPlain:
                    pwdEl.SetAttribute("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
                    pwdEl.InnerText = Password;
                    usernameTokenEl.AppendChild(pwdEl);
                    break;
            }
    
            return usernameTokenEl;
        }
    
        private byte[] GenerateNonce(int bytes)
        {
            byte[] output = new byte[bytes];
            Random r = new Random(DateTime.Now.Millisecond);
            r.NextBytes(output);
            return output;
        }
    
        private static byte[] SHA1Hash(byte[] input)
        {
            SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
            return sha1Hasher.ComputeHash(input);
        }
    
        private static string ToBase64(byte[] input)
        {
            return Convert.ToBase64String(input);
        }
    }
    

    }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 2015-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多