问题
I am developing a ONVIF driver using .NET 4 (Windows Forms, not WCF). I started importing WSDL files as a service in visual studio. So I am able to send command to a device in this way:
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();
But I am not able to manage HTTP digest authentication. I spent days searching on google examples and solutions, but the only ways seems to be hand write XML code. There is not any clean solution like:
deviceClient.ChannelFactory.Credentials.HttpDigest.ClientCredential.UserName = USERNAME;
deviceClient.ChannelFactory.Credentials.HttpDigest.ClientCredential.Password = digestPassword;
(that doesn't work)?
回答1:
First of all you should install Microsoft.Web.Services3 package. (View> Other windows> Package manager console). Then you must add digest behavior to your endpoint. The first part of the code is PasswordDigestBehavior class and after that it is used for connecting to an ONVIF device service.
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;
}
}
And this is how to use it:
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);
回答2:
For future readers, finally I was able to perform both type of authentication without using WSE 3.0. This is partial code (for shortness), based on the IClientMessageInspector interface (you can find lot of other examples based on this interface):
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;
}
来源:https://stackoverflow.com/questions/43272227/onvif-wsdl-service-unable-to-authenticate