ONVIF wsdl 服务:无法进行身份验证



我正在使用.NET 4(Windows Forms,而不是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;

(那行不通(?

首先,您应该安装 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);

对于未来的读者,我终于能够在不使用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;
    }

此类应该能够替换 WSE 用户名令牌对象并删除对 WSE 的依赖。它还使得在IClientInspector中搜索和修复随机数变得不必要。我只在 1 台相机上测试过它,并且只使用散列密码。扬子晚报.

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

}

最新更新